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.
- Information:
- Information events describe things happening in the system that correspond to its responsibilities and functions. Generally these are the observable actions the system can perform.
- 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 via Debug Keys (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.
Key | Value |
---|---|
global.logging.loggers | audit |
audit:serilog:using:File | Serilog.Sinks.File |
audit:serilog:write-to:File.path | <c:\full path\to log file\filename.log> |
audit:serilog:write-to:File.rollingInterval | Day |
audit:serilog:write-to:File.formatter | Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact |
audit:serilog:using:FilterExpressions | Serilog.Filters.Expressions |
audit:serilog:filter:ByIncludingOnly.expression | Has(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 name | Description | Parameters | Parameter Description |
---|---|---|---|
AcceptChatRequestAuditEntry | Accept-chat-request operation. | RequestId | Gets the request ID of the chat request to accept. |
AddContactsAuditEntry | The add contacts operation. | ContactPreferences | Gets the contact to be added |
CloseMultipartyChatAuditEntry | The close multiparty chat operation. | MultipartyChatId | Gets the ID of the multiparty chat to close. |
CreateGroupAuditEntry | The create group operation. | Name | Gets the name of the created group. |
CategoryId | Gets the ID of the category that the created group belongs to. | ||
Description | Gets the description of the created group. | ||
Privacy | Gets the privacy of the created group. | ||
Members | Gets the collection of IDs representing the members of the created group. | ||
Managers | Gets the collection of IDs representing the managers of the created group. | ||
Presenters | Gets the collection of IDs representing the presenters of the created group. | ||
IsAuditorium | Gets the value determining whether the created group is an auditorium. | ||
CreateMultipartyChatAuditEntry | The create multiparty chat operation. | MultipartyChatId | Gets the multiparty chat ID. |
Subject | Gets the subject. | ||
UserIds | Gets the user IDs. | ||
DeleteGroupAduitEntry | The delete group operation. | GroupId | Gets the ID of the deleted group. |
DownloadFileAuditEntry | The upload-file operation. | FileId | Gets the file ID. |
ChatId | Gets the chat ID. | ||
EscalateToMultipartyChatAuditEntry | The escalate to multiparty chat operation. | MultipartyChatId | Gets the multiparty chat ID. |
EscalatingChatId | Gets the escalating chat ID. | ||
Subject | Gets the subject. | ||
InvitedUserIds | Gets the invited user IDs. | ||
FindPrincipalsForRoleAuditEntry | Finds principals for a given role. | GroupId | Gets the group ID. |
Role | Gets the role with which principals will be searched for. | ||
SearchTerm | Gets the search term. | ||
FindPrincipalsInCategoryAuditEntry | Finds principals for a given category. | CategoryId | Gets the category ID. |
SearchTerm | Gets the search term. | ||
GetUserMetadataAuditEntry | The get user metadata operation. | UserIds | Gets the user IDs. |
GetGroupCategoriesAuditEntry | Gets categories for all groups. | ||
GetGroupManagersAuditEntry | Gets the managers for a given group. | GroupId | Gets the group ID. |
GetGroupMembersAuditEntry | Gets the members for a given group. | GroupId | Gets the group ID. |
GetGroupPresentersAuditEntry | Gets the presenters for a given group. | GroupId | Gets the group ID. |
GetManagedGroupsAuditEntry | Gets the groups managed by the connector identity. | ||
GetManagedGroupsFromIdsAuditEntry | Gets the groups, with the given group IDs. | GroupIds | Gets the group IDs. |
IncomingChatStateChangedAuditEntry | The incoming chat state changed event. | IncomingChatState ChangedEventArgs | Gets the incoming chat state changed event arguments. |
InviteMultipartyChatUsersAuditEntry | The invite multiparty chat users operation. | MultipartyChatId | Gets the multiparty chat ID. |
UserIds | Gets the user IDs. | ||
JoinMultipartyChatAuditEntry | The join multiparty chat operation. | MultipartyChatId | Gets the multiparty chat ID |
LoadContactPreferencesAuditEntry | Loading contact preferences. | ForceLatest | Gets a value indicating whether the latest value should be fetched from the underlying system. |
LoadDockAuditEntry | Loading the dock structure. | ForceLatest | Gets a value indicating whether the latest value should be fetched from the underlying system. |
LogOnAuditEntry | Logging on. | ClaimsIdentity | Gets the claims identity. |
UserId | Gets the user ID, or null if log on is not successful. | ||
LogOnIdentityResolutionAuditEntry | Resolution of a log on identity. | ClaimsIdentity | Get the claims identity |
MessageReceivedAuditEntry | The message received event. | MessageReceived EventArgs | Gets the message received event arguments. |
MultipartyChatActiveModalitiesChangedAuditEntry | The multiparty chat active modalities changed event. | MultipartyChatId | Gets the multiparty chat ID. |
ActiveModalities | Gets the active modalities in the chat. | ||
MultipartyChatEndedAuditEntry | The multiparty chat ended event. | MultipartyChatId | Gets the multiparty chat ID. |
MultipartyChat EndedReason | Gets the multiparty chat ended reason. | ||
MultipartyChatEscalationStartedAuditEntry | The multiparty chat escalation started event. | MultipartyChatId | Gets the multiparty chat ID. |
OriginatorPrivate ConversationId | Gets the originator private conversation ID. | ||
Subject | Gets the subject. | ||
MultipartyChatInvitationCompletedAuditEntry | A completed multiparty chat invitation. | MultipartyChat InvitationResult | Gets the multiparty chat invitation result. |
InvitedUserId | Gets the invited user ID. | ||
MultipartyChatId | Gets the multiparty chat ID. | ||
MultipartyChatInvitationReceivedAuditEntry | The multiparty chat invitation received event. | MultipartyChatId | Gets the multiparty chat ID. |
Subject | Gets the subject. | ||
ActiveModalities | Gets the active modalities. | ||
MultipartyChatJoinedAuditEntry | The multiparty chat joined event. | MultipartyChatId | Gets the multiparty chat ID. |
Participants | Gets the participants. | ||
MultipartyChatLobbyEnteredAuditEntry | The multiparty chat lobby entered event. | MultipartyChatId | Gets the multiparty chat ID. |
MultipartyChatParticipantModalitiesChangedAuditEntry | The multiparty chat participant modalities changed event. | MultipartyChatId | Gets the ID of the multiparty chat. |
ParticipantId | Gets the ID of the participant. | ||
ActiveModalities | Gets the active modalities. | ||
IsAudioMuted | Gets a value indicating whether the participant audio is muted. | ||
MultipartyChatParticipantsChangedAuditEntry | The multiparty chat participants changed event. | MultipartyChatId | Gets the multiparty chat ID. |
ParticipantChanges | Gets the participant changes. | ||
OpenChatAuditEntry | The open-chat operation. | ChatId | Gets the chat ID. |
SaveDockAuditEntry | The save dock operation. | DockStructure | Gets the dock structure. |
SearchUsersAuditEntry | The search users operation. | SearchTerm | Gets the search term. |
SecurityIdentityResolutionAuditEntry | Resolution of a security identity. | ClaimsIdentity | Gets the claims identity. |
SendMessageAuditEntry | The send-message operation. | ChatId | Gets the chat ID. |
MessageContent | Gets the message content. | ||
MessageMetadata | Gets the message metadata. | ||
SetGroupManagersAuditEntry | The set group managers operation. | GroupId | Gets the group ID. |
Managers | Gets the collection of IDs representing the group's managers. | ||
SetGroupMembersAuditEntry | The set group members operation. | GroupId | Gets the group ID. |
Members | Gets the collection of IDs representing the group's members. | ||
SetGroupPresentersAuditEntry | The set group presenters operation. | GroupId | Gets the group ID. |
Presenters | Gets the collection of IDs representing the group's presenters. | ||
SubscriberNotificationReceivedAuditEntry | The subscriber notification received event. | Subscribers | Gets the subscribers. |
SubscribeUsersAuditEntry | The subscribe users operation. | UserIds | Gets the user IDs. |
SubscriptionMode | Gets the subscription mode. | ||
UpdateGroupMetadataAuditEntry | The update group metadata audit entry. | GroupId | Gets group ID. |
Metadata | Gets the group metadata. | ||
UploadFileAuditEntry | The upload-file operation. | FileName | Gets the file name. |
ChatId | Gets the chat ID. | ||
UserMetadataReceivedAuditEntry | The user metadata received event. | UserMetadataUpdate | Gets 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.
Key | Value |
---|---|
global.logging.loggers | LogOn,Metadata |
LogOn:serilog:using:File | Serilog.Sinks.File |
LogOn:serilog:write-to:File.pathFormat | <c:\full path\to log file\audit-logon.log> |
LogOn:serilog:write-to:File.rollingInterval | Day |
LogOn:serilog:write-to:File.formatter | Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact |
LogOn:serilog:using:FilterExpressions | Serilog.Filters.Expressions |
LogOn:serilog:filter:ByIncludingOnly.expression | Has(AuditEntry) and TypeOf(AuditEntry) = 'LogOnAuditEntry' |
Metadata:serilog:using:File | Serilog.Sinks.File |
Metadata:serilog:write-to:File.path | <c:\full path\to log file\audit-metadata.log> |
Metadata:serilog:write-to:File.rollingInterval | Day |
Metadata:serilog:write-to:File.formatter | Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact |
Metadata:serilog:using:FilterExpressions | Serilog.Filters.Expressions |
Metadata:serilog:filter:ByIncludingOnly.expression | Has(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.