Logging

Configuring the logger via the Serilog configuration file#

While running the service it is possible to configure the logger directly via a <install directory>\Config\serilog.json file.

This is what the default file looks like to enable a file logger for verbose messages and event log for errors:

{
"Serilog":{
"MinimumLevel": "Verbose",
"Using": ["MindLink.Core.Common", "Serilog.Sinks.File", "Serilog.Sinks.EventLog" ],
"WriteTo:Main": {
"Name": "Logger",
"Args": {
"configureLogger": {
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "<install directory>\\logs\\Connector.log",
"rollingInterval": "Day",
"fileSizeLimitBytes": "100000000",
"rollOnFileSizeLimit": "true",
"outputTemplate": "[{Level}] {Timestamp:yyyy-MM-ddTHH:mm:ss.fffZ} {MachineName} [{SourceContext}] {Message:lj}{NewLine}{Exception}",
"useMindLinkFormatter": "true"
}
}]
}
}
},
"WriteTo:Event": {
"Name": "EventLog",
"Args": {
"source": "Connector Server",
"restrictedToMinimumLevel": "Error"
}
}
}
}

Some changes to the configuration file can be made while the service is running. The following relevant properties can be updated dynamically:

  • Logging Levels
  • Logging Level Overrides (existing overrides only)

Other changes will require a host restart before taking effect.

Enabling Auditing#

The following sample demonstrates how to add a sub-logger for auditing.

{
"Serilog":{
"MinimumLevel": "Verbose",
"Using": ["MindLink.Core.Common", "Serilog.Sinks.File", "Serilog.Sinks.EventLog" ],
"WriteTo:Main": {
"Name": "Logger",
"Args": {
"configureLogger": {
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "<install directory>\\logs\\Connector.log",
"rollingInterval": "Day",
"fileSizeLimitBytes": "100000000",
"rollOnFileSizeLimit": "true",
"outputTemplate": "[{Level}] {Timestamp:yyyy-MM-ddTHH:mm:ss.fffZ} {MachineName} [{SourceContext}] {Message:lj}{NewLine}{Exception}",
"useMindLinkFormatter": "true"
}
}]
}
}
},
"WriteTo:Audit": {
"Name": "Logger",
"Args": {
"configureLogger": {
"Filter": [
{
"Name": "ByIncludingOnly",
"Args": {
"expression": "Has(AuditEntry)"
}
}
],
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "%PROGRAMDATA%\\MindLink\\audit.log",
"rollingInterval": "Day",
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter,Serilog.Formatting.Compact",
"fileSizeLimitBytes": "100000000",
"rollOnFileSizeLimit": "true"
}
}]
}
}
},
"WriteTo:Event": {
"Name": "EventLog",
"Args": {
"source": "Connector Server",
"restrictedToMinimumLevel": "Error"
}
}
}
}

Logging levels and "MinimumLevel"#

Serilog separates logging into muliple levels:

  • Verbose:
    • The noisiest logging level.
  • Debug:
    • Typically used for internal system events that are not usually viewable from the outside.
  • Warning:
    • When the service is endangered or may be behaving outside its expected parameters.
  • Error:
    • When functionality is unavailable or expectations broken.
  • Fatal:
    • The most critical level.

By setting the MinimumLevel of the logger you can restrict or reveal more information provided by the logger.

Overriding logging levels for certain namespaces#

Some namespaces are more likely to output a large amount of logs. You can control the output of the logger based on the namespace the logs were sent from. The example below demonstrates how this can be configured in the JSON.

{
"Serilog":{
"MinimumLevel" : {
"Default" : "Error",
"Override" : {
"Orleans" : "Warning",
"MindLink" : "Verbose"
}
...
}

Sending output to different sinks and Filters#

As you may have noticed in Enabling Auditing above, it is possible to have multiple loggers for a variety of purposes. Filters can be applied to any logger to remove unnecessary information.

{
"Serilog":{
"MinimumLevel": "Verbose",
"Using": ["MindLink.Core.Common", "Serilog.Sinks.File", "Serilog.Sinks.EventLog" ],
"WriteTo:Main": {
"Name": "Logger",
"Args": {
"configureLogger": {
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "logs\\Connector.log",
"rollingInterval": "Day",
"fileSizeLimitBytes": "100000000",
"rollOnFileSizeLimit": "true",
"outputTemplate": "[{Level}] {Timestamp:yyyy-MM-ddTHH:mm:ss.fffZ} {MachineName} [{SourceContext}] {Message:lj}{NewLine}{Exception}",
"useMindLinkFormatter": "true"
}
}]
}
}
},
"WriteTo:MceAdmin": {
"Name": "Logger",
"Args": {
"configureLogger": {
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "logs2\\MceAdmin.log",
"rollingInterval": "Day",
"fileSizeLimitBytes": "100000000",
"rollOnFileSizeLimit": "true",
"outputTemplate": "[{Level}] {Timestamp:yyyy-MM-ddTHH:mm:ss.fffZ} {MachineName} [{SourceContext}] {Message:lj}{NewLine}{Exception}",
"useMindLinkFormatter": "true"
}
}],
"Filter" : [
{
"Name": "ByIncludingOnly",
"Args": {
"expression": "StartsWith(SourceContext, 'MindLink.Core.MceAdmin')"
}
}
]
}
}
},
...
}

Filters require an expression that will be used to filter the logs. The example above makes use of the SourceContext to match for any namespace starting with "MindLink.Core.MceAdmin". Given that filtered logs are from an additional sub-logger, the resulting log file can be output to another path. The "path" argument determines the location where the file will be output.


Advanced Examples#

Segregated log files by service domains#

Example file: serilog.config

This example demonstrates how to create separate log files for different parts of the service:

  • Logon attempts + failures => %PROGRAMDATA%\MindLink\audit-logons.log
  • MCE backend => %PROGRAMDATA%\MindLink\mce.log
  • Orleans platform => %PROGRAMDATA%\MindLink\orleans.log
  • ASPNet Core internals (Web services) => %PROGRAMDATA%\MindLink\aspnet.log
  • Anything else (user front-end sessions and connectivity to backend services) => %PROGRAMDATA%\MindLink\service.log
  • All Errors => Event log

This example has a global logging level of Verbose, while sub-loggers override the minimum level to Warning. This could be modified so that the global logging level controls the level of all sub-loggers by removing the minimum level restriction on the sub-loggers.

Audit Logging (Legacy)#

Configuration#

Deploying the configuration#

First, you will need to enable audit logging from the Logging section of the Management Center.

The best way to add audit logging configuration is through the advanced tab of the Management Center. See the Advanced tab section for how to add the keys.

KeyValue
global.logging.loggersaudit
audit:serilog:using:FileSerilog.Sinks.File
audit:serilog:write-to:File.path<c:\full path\to log file\filename.log>
audit:serilog:write-to:File.rollingIntervalDay
audit:serilog:write-to:File.formatterSerilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact
audit:serilog:using:FilterExpressionsSerilog.Filters.Expressions
audit:serilog:filter:ByIncludingOnly.expressionHas(AuditEntry)

With this I now have a log file at the path "<c:\full path\to log file\filename.log>" that outputs all audit results.


Audit Entry Types#

The configuration shown above will create a basic audit log of all event types, but the filter key shown at the bottom of the configuration keys can use more complex expressions to refine the content of the log.

By enabling audit logging and configuring as above, the created log file will record all available Audit entry types.

Audit entry nameDescriptionParametersParameter Description
AcceptChatRequestAuditEntryAccept-chat-request operation.RequestIdGets the request ID of the chat request to accept.
AddContactsAuditEntryThe add contacts operation.ContactPreferencesGets the contact to be added
CloseMultipartyChatAuditEntryThe close multiparty chat operation.MultipartyChatIdGets the ID of the multiparty chat to close.
CreateGroupAuditEntryThe create group operation.NameGets the name of the created group.
CategoryIdGets the ID of the category that the created group belongs to.
DescriptionGets the description of the created group.
PrivacyGets the privacy of the created group.
MembersGets the collection of IDs representing the members of the created group.
ManagersGets the collection of IDs representing the managers of the created group.
PresentersGets the collection of IDs representing the presenters of the created group.
IsAuditoriumGets the value determining whether the created group is an auditorium.
CreateMultipartyChatAuditEntryThe create multiparty chat operation.MultipartyChatIdGets the multiparty chat ID.
SubjectGets the subject.
UserIdsGets the user IDs.
DeleteGroupAduitEntryThe delete group operation.GroupIdGets the ID of the deleted group.
DownloadFileAuditEntryThe upload-file operation.FileIdGets the file ID.
ChatIdGets the chat ID.
EscalateToMultipartyChatAuditEntryThe escalate to multiparty chat operation.MultipartyChatIdGets the multiparty chat ID.
EscalatingChatIdGets the escalating chat ID.
SubjectGets the subject.
InvitedUserIdsGets the invited user IDs.
FindPrincipalsForRoleAuditEntryFinds principals for a given role.GroupIdGets the group ID.
RoleGets the role with which principals will be searched for.
SearchTermGets the search term.
FindPrincipalsInCategoryAuditEntryFinds principals for a given category.CategoryIdGets the category ID.
SearchTermGets the search term.
GetUserMetadataAuditEntryThe get user metadata operation.UserIdsGets the user IDs.
GetGroupCategoriesAuditEntryGets categories for all groups.
GetGroupManagersAuditEntryGets the managers for a given group.GroupIdGets the group ID.
GetGroupMembersAuditEntryGets the members for a given group.GroupIdGets the group ID.
GetGroupPresentersAuditEntryGets the presenters for a given group.GroupIdGets the group ID.
GetManagedGroupsAuditEntryGets the groups managed by the connector identity.
GetManagedGroups FromIdsAuditEntryGets the groups, with the given group IDs.GroupIdsGets the group IDs.
IncomingChatState ChangedAuditEntryThe incoming chat state changed event.IncomingChatState ChangedEventArgsGets the incoming chat state changed event arguments.
InviteMultipartyChatUsersAuditEntryThe invite multiparty chat users operation.MultipartyChatIdGets the multiparty chat ID.
UserIdsGets the user IDs.
JoinMultipartyChatAuditEntryThe join multiparty chat operation.MultipartyChatIdGets the multiparty chat ID
LoadContactPreferencesAuditEntryLoading contact preferences.ForceLatestGets a value indicating whether the latest value should be fetched from the underlying system.
LoadDockAuditEntryLoading the dock structure.ForceLatestGets a value indicating whether the latest value should be fetched from the underlying system.
LogOnAuditEntryLogging on.ClaimsIdentityGets the claims identity.
UserIdGets the user ID, or null if log on is not successful.
LogOnIdentityResolutionAuditEntryResolution of a log on identity.ClaimsIdentityGet the claims identity
MessageReceivedAuditEntryThe message received event.MessageReceived EventArgsGets the message received event arguments.
MultipartyChatActive ModalitiesChangedAuditEntryThe multiparty chat active modalities changed event.MultipartyChatIdGets the multiparty chat ID.
ActiveModalitiesGets the active modalities in the chat.
MultipartyChatEndedAuditEntryThe multiparty chat ended event.MultipartyChatIdGets the multiparty chat ID.
MultipartyChat EndedReasonGets the multiparty chat ended reason.
MultipartyChat EscalationStartedAuditEntryThe multiparty chat escalation started event.MultipartyChatIdGets the multiparty chat ID.
OriginatorPrivate ConversationIdGets the originator private conversation ID.
SubjectGets the subject.
MultipartyChat InvitationCompletedAuditEntryA completed multiparty chat invitation.MultipartyChat InvitationResultGets the multiparty chat invitation result.
InvitedUserIdGets the invited user ID.
MultipartyChatIdGets the multiparty chat ID.
MultipartyChatInvitation ReceivedAuditEntryThe multiparty chat invitation received event.MultipartyChatIdGets the multiparty chat ID.
SubjectGets the subject.
ActiveModalitiesGets the active modalities.
MultipartyChatJoinedAuditEntryThe multiparty chat joined event.MultipartyChatIdGets the multiparty chat ID.
ParticipantsGets the participants.
MultipartyChatLobby EnteredAuditEntryThe multiparty chat lobby entered event.MultipartyChatIdGets the multiparty chat ID.
MultipartyChatParticipant ModalitiesChangedAuditEntryThe multiparty chat participant modalities changed event.MultipartyChatIdGets the ID of the multiparty chat.
ParticipantIdGets the ID of the participant.
ActiveModalitiesGets the active modalities.
IsAudioMutedGets a value indicating whether the participant audio is muted.
MultipartyChatParticipants ChangedAuditEntryThe multiparty chat participants changed event.MultipartyChatIdGets the multiparty chat ID.
ParticipantChangesGets the participant changes.
OpenChatAuditEntryThe open-chat operation.ChatIdGets the chat ID.
SaveDockAuditEntryThe save dock operation.DockStructureGets the dock structure.
SearchUsersAuditEntryThe search users operation.SearchTermGets the search term.
SecurityIdentity ResolutionAuditEntryResolution of a security identity.ClaimsIdentityGets the claims identity.
SendMessageAuditEntryThe send-message operation.ChatIdGets the chat ID.
MessageContentGets the message content.
MessageMetadataGets the message metadata.
SetGroupManagersAuditEntryThe set group managers operation.GroupIdGets the group ID.
ManagersGets the collection of IDs representing the group's managers.
SetGroupMembersAuditEntryThe set group members operation.GroupIdGets the group ID.
MembersGets the collection of IDs representing the group's members.
SetGroupPresentersAuditEntryThe set group presenters operation.GroupIdGets the group ID.
PresentersGets the collection of IDs representing the group's presenters.
SubscriberNotification ReceivedAuditEntryThe subscriber notification received event.SubscribersGets the subscribers.
SubscribeUsersAuditEntryThe subscribe users operation.UserIdsGets the user IDs.
SubscriptionModeGets the subscription mode.
UpdateGroupMetadataAuditEntryThe update group metadata audit entry.GroupIdGets group ID.
MetadataGets the group metadata.
UploadFileAuditEntryThe upload-file operation.FileNameGets the file name.
ChatIdGets the chat ID.
UserMetadataReceivedAuditEntryThe user metadata received event.UserMetadataUpdateGets the user metadata update.

Create multiple Audit logs#

It is possible to create multiple audit logs for different events. global.logging.logger is grouping the debug keys that follow which begin with its value.

The configuration example shows audit: before each of the non-global configurations, because this is the global.logging.logger's value.

KeyValue
global.logging.loggersLogOn,Metadata
LogOn:serilog:using:FileSerilog.Sinks.File
LogOn:serilog:write-to:File.pathFormat<c:\full path\to log file\audit-logon.log>
LogOn:serilog:write-to:File.rollingIntervalDay
LogOn:serilog:write-to:File.formatterSerilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact
LogOn:serilog:using:FilterExpressionsSerilog.Filters.Expressions
LogOn:serilog:filter:ByIncludingOnly.expressionHas(AuditEntry) and TypeOf(AuditEntry) = 'LogOnAuditEntry'
Metadata:serilog:using:FileSerilog.Sinks.File
Metadata:serilog:write-to:File.path<c:\full path\to log file\audit-metadata.log>
Metadata:serilog:write-to:File.rollingIntervalDay
Metadata:serilog:write-to:File.formatterSerilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact
Metadata:serilog:using:FilterExpressionsSerilog.Filters.Expressions
Metadata:serilog:filter:ByIncludingOnly.expressionHas(AuditEntry) and TypeOf(AuditEntry) = 'GetUserMetadataAuditEntry'

In this example two audit logs are created, with a filter expression to target different types of event. LogOn will create a log file of user log on events, while the other log will contain Metadata event types.

See the above Audit Entry Types section for more coverage of available parameters and other available expressions.