Architecture

censhare Server uses a combination the standard Java logging, SLF4J and Logback. On the highest level, all code written by censhare uses an extension of the standard Java logger called ContextLogger to collect log messages. The ContextLogger allows to increase the log level and redirect log messages depending on the context of a service call.

All log messages from the standard Java logging, Apache Log4j and Apache commons logging are consolidated using the SLF4J interface and the appropriate bridges. Due to the logging levels available in SLF4J, the level “finer” from standard Java logging is elevated to “fine”.

The actual logging is done through Logback which implements the SLF4J interface and is managed through the standard censhare Server configuration mechanism as described below. The default configuration logs the server messages to censhare-Server/work/logs/server-0.x.log. In addition, logging to the console, syslog and a censhare remote logging server can be configured.

Usage

Use the ContextLogger to obtain a logger. Please don’t use the logging level “finer”, because it will be elevated to “fine” in the framework.

package com.censhare.server.demo;
 
import java.util.logging.Logger;
 
import com.censhare.support.util.logging.ContextLogger;
 
public class Demo {
 
     private static final Logger logger = ContextLogger.getLogger(Demo. class );
 
     public Demo() {
         logger.info( "Demo" );
     }
 
}

If the logging output is on a performance critical path and also uses resources (e.g. string concatenation and formatting), it should either be wrapped in a level check or implemented using a supplier. Always use isLoggable() for the level check and not getLevel(), because it will not reflect the effective log level in all cases.

if (logger.isLoggable(Level.FINE)) {
     logger.fine( "Fine message " + param);
}
 
logger.fine(() -> "Fine message " + param);

It is important to never use setLevel() on a logger, because it will not be propagated correctly through the logging framework. Instead, manipulate the log levels using the server configuration or change it from the Java code by using configureLogger() on the CenShareServer instance.

CenShareServer.getInstance().configureLogger(logger.getName(), Level.FINEST, true );

Configuration

To increase the level of specific loggers, and/or the separation of their messages to a separated file, different logger handlers can be specified in the server.xml configuration. It is not needed to temporary increase the level of messages for that matter.

In the example below, the output of all messages related to ModuleService is forwarded to a separated file, similarly as all workspace-related output.

< logging reset = "true" >
    <!-- root logger -->
    < logger name = "" level = "INFO" />
    <!-- server main thread context logger -->
    < logger name = "com.censhare.server.kernel.CenShareServer" level = "INFO" />
    <!-- command transaction context logger -->
    < logger name = "com.censhare.server.kernel.Command" level = "FINE" />
    <!-- avoid logging of "java.net.SocketTimeoutException: Accept timed out" execptions with jdk6 -->
    < logger name = "sun.rmi.transport.tcp" level = "OFF" />
    <!-- log external REST requests with level FINE or FINER -->
    < logger name = "com.censhare.server.webservice.AuthenticationFilter" level = "FINE" />
    < handler type = "file" level = "INFO" file-limit = "10000000" file-count = "1" append = "false" filename-pattern = "file:work/logs/server-%u.%g.log" formatter = "com.censhare.support.util.logging.SimpleFormatter" >
      < logger name = "" />
    </ handler >
    < handler type = "file" level = "INFO" file-limit = "10000000" file-count = "5" append = "false" filename-pattern = "file:work/logs/client-%u.%g.log" formatter = "com.censhare.support.util.logging.SimpleFormatter" >
      < logger name = "censhare5.client" use-parent-handlers = "false" />
    </ handler >
 
   < handler type = "file" level = "INFO" file-limit = "10000000" file-count = "10" append = "true" filename-pattern = "file:work/logs/database-update-%u.%g.log" formatter = "com.censhare.support.util.logging.SimpleFormatter" >
      < logger name = "com.censhare.manager.databasemanager.DatabaseUpdater" use-parent-handlers = "false" />
    </ handler >
 
   < handler type = "file" level = "FINEST" file-limit = "10000000" file-count = "1" append = "false" filename-pattern = "file:work/logs/server-request-debug-%u.%g.log" formatter = "com.censhare.support.util.logging.SimpleFormatter" >
        < logger name = "com.censhare.api.query.QueryCommand" level = "FINEST" />
   </ handler >
 
   < handler type = "file" level = "FINEST" file-limit = "10000000" file-count = "1" append = "false" filename-pattern = "file:work/logs/server-module-service-%u.%g.log" formatter = "com.censhare.support.util.logging.SimpleFormatter" >
      < logger name = "com.censhare.manager.module.ModuleServiceImpl$ModuleConfigurationImpl" level = "FINER" />
      < logger name = "com.censhare.manager.module.dependency.ModuleUtil" level = "FINEST" />
      < logger name = "com.censhare.manager.module.java.JavaCodeManagerImpl" level = "FINEST" />
      < logger name = "com.censhare.manager.clientdelivery.impl.DeliveryServlet" level = "FINEST" />
      < logger name = "com.censhare.manager.module.typescript.TypeScriptCodeManager" level = "FINEST" />
   </ handler >
 
 
   < handler type = "file" level = "FINEST" file-limit = "10000000" file-count = "1" append = "false" filename-pattern = "file:work/logs/server-workspace-%u.%g.log" formatter = "com.censhare.support.util.logging.SimpleFormatter" >
      < logger name = "com.censhare.api.workspace.WorkspaceHandlers" level = "FINEST" />
      < logger name = "com.censhare.api.workspace.WorkspaceHandlers$OpenWorkspace" level = "FINEST" />
      < logger name = "com.censhare.api.workspace.WorkspaceHandlers$GetPage" level = "FINEST" />
      < logger name = "com.censhare.api.workspace.WorkspaceHandlers$GetAllPages" level = "FINEST" />
      < logger name = "com.censhare.api.workspace.WorkspaceHandlers$ReadPreferences" level = "FINEST" />
      < logger name = "com.censhare.api.workspace.WorkspaceHandlers$SavePreferences" level = "FINEST" />
      < logger name = "com.censhare.api.workspace.WorkspaceHandlers$ResetPreferences" level = "FINEST" />
      < logger name = "com.censhare.api.workspace.WorkspaceHandlers$ReceiveEvents" level = "FINEST" />
      < logger name = "com.censhare.api.session.SessionCommandHandlers$Login" level = "FINEST" />
      < logger name = "com.censhare.api.workspace.mergemodel.ObservableProcessor" level = "FINEST" />
      < logger name = "com.censhare.api.workspace.Workspace" level = "FINEST" />
   </ handler >

A logging message will appear only when both the logger’s and the handler’s levels are set to “message level” .

The exact name of the logger can be found on its definition e.g.:

private static final Logger logger = ContextLogger.getLogger(QueryCommand. class );

Normally loggers are named using the full class name.

Additionally, XML patching can be used as an alternative way to change the logging configuration.

Runtime configuration

The log levels for all loggers and handlers can be changed during the server runtime, using the server action “Logging Manager”. The server action needs to be enabled first in “Configuration > Modules > Administration > Logging Manager”.

The log level for specific server actions can be changed in their respective command XML:

< cmd >
     < cmd-info
         log-level = "INFO"
     />
</ cmd >

Loggstash server monitoring

From version 2018.1, it is possible to monitor the server through JSON messages sent to Logstash.

Configuration

In order to use this monitoring solution, you must first have a running Logstash server configured to handle JSON input and accept connections from the censhare Server.

To configure the connection to Logstash, add the monitoring settings and its attached log handler configurations through your censhare-Custom/app/config/server.patch file. For more information on this, please refer to the article “XML patching”.

<? xml version = "1.0" encoding = "UTF-8" ?>
< p:patch xmlns:p = "http://www.censhare.com/xml/3.0.0/xml-patch"
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://www.censhare.com/xml/3.0.0/xml-patch http://www.censhare.com/xml/3.0.0/xml-patch.xsd" >
   
   < p:replace-attr select = "/serverconfig/server-core" attr = "enable-monitor" value = "true" />
   < p:replace-attr select = "/serverconfig/server-core" attr = "monitor-interval" value = "" />
   
   < p:add select = "/serverconfig/logging/context" pos = "before" >
       < handler
         type = "custom"
         level = "INFO"
         class = "com.censhare.server.logging.ConfigurableRemoteAppender"
         queue-file = "file:work/logs/queue.db"
         encoder = "monitoring-json"
         hostname = ""
         port = ""
         keystore = "file:PATH-TO-KEYSTORE"
         keystore-password = ""
         keystore-alias = "" >
         < logger name = "server.monitor" level = "INFO" use-parent-handlers = "false" />
     </ handler >
   </ p:add >
</ p:patch >

Add appropriate values to the following attributes to configure the Logstash server monitoring:

  • enable-monitor: if the monitoring is enabled or not for the server;
  • monitor-interval: the time in seconds between censhare Server status messages sent to Logstash;
  • hostname: hostname of the Logstash server;
  • port: connection port to the Logstash server;
  • keystore: replace PATH-TO-KEYSTORE with the path to the keystore file in the censhare Server;
  • keystore-alias: alias to be used on the keystore file;
  • keystore-password: password to be used on the keystore file.

Monitored information

When the Logstash server monitoring is enabled, the server periodically sends JSON messages containing the following information:

  • connected_users: an array of JSON objects representing an active connection to the censhare Server. Each connection is represented by the following:
    • host: host of the connected user;
    • loginname: username of the connected user;
    • client-type: the type of client used by the connected user;
    • client-version: the version of the client used by the connected user;
  • memory: the amount of memory used by the server, in megabytes;
  • max_memory: the maximum amount of memory usable by the server, in megabytes;
  • total_memory: the total amount available memory on the server, in megabytes;
  • jvm: a JSON object representing the current JVM state, listing the following information:
    • total_memory: the total amount available memory on the server, in bytes;
    • max_memory: the maximum amount of memory usable by the server, in bytes;
    • free_memory: the amount of free memory on the server, in bytes;
    • available_processors: the number of processors available on the server;
    • threads: an array of JSON objects representing the current threads in the JVM. Each thread is represented by:
      • group: the group which the thread belongs to;
      • name: the thread name;
      • priority: the thread priority;
      • is_alive: if the thread is alive;
      • is_interrupted: if the thread is interrupted;
  • static_services: an array of JSON objects representing the currently running static services in the server. Each service is represented by:
    • key: the service key;
    • simple_class_name: the class name of the service;
    • description: the service description;
    • local: if the service is local;
    • invocation_count_average: the average of service invocations over the last 5 seconds;
    • last_access: the timestamp of the last invocation the service;
    • access_count: the number of invocations to the service;
    • access_limit: the limit of invocations of the service allowed;
    • state: the current state of the service;
    • in_use: if the service is in use;
    • distance: the cost of the service invocation;
    • dependencies_on_us: if there are other services that depend on this one;
    • foreign_dependencies: if there are active services that depend on this one;
    • dependencies_available: if the other services which this one depends on are available.

Writing your own messages to Logstash

It is possible to write your own messages to be sent by the censhare Server. To do so, simply send your messages through the monitoring Logger object:

Logger logger = Logger.getLogger( "server.monitor" )

The messages sent through the server monitoring logger must be formatted as JSON.

JSONObject messageJson = new JSONObject();
messageJson.put( "single_message" , "This is a sample message" );
logger.info(messageJson.toString());