Configuration

Example use in a console app

Create an appender with the following XML configuration:

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="CloudLogger" type="Google.Cloud.Logging.Log4Net.GoogleStackdriverAppender,Google.Cloud.Logging.Log4Net">
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message" />
    </layout>
    <projectId value="PROJECT_ID" />
    <logId value="LOG_ID" />
  </appender>
  <root>
    <level value="ALL" />
    <appender-ref ref="CloudLogger" />
  </root>
</log4net>

Save this as log4net.xml in your application directory. Then load it during program startup:

using log4net;
using log4net.Config;

public static void Main(string[] args)
{
    XmlConfigurator.Configure(new FileInfo("log4net.xml"));
}

Logged items will now be sent to Google Cloud Logging:

using log4net;

ILog log = LogManager.GetLogger(typeof(Program));
log.Info("An exciting log entry!");

Custom configuration

Within a GoogleStackdriverAppender the following options are available (see also the GoogleStackdriverAppender reference documentation for more details):

projectId

<projectId value="<your project ID>"/>

Optional when running on Google App Engine (GAE) or Google Compute Engine (GCE), must be present on other platforms. The project ID given must be the ID of a project that already exists on Google Cloud.

On GCE and GAE the project ID will be auto-detected from the platform.

logId

<logId value="<your log ID>"/>

Must be present. The log ID given is any string that is recorded as the name of the log, which can be used for simple filtering when viewing log entries.

Additional metadata

(Added with the AddWithMetaData method.)

<withMetaData value="<various, see below>"/>

By default no extra metadata is logged. withMetaData specifies which extra metadata to log with each log entry, and can be specified multiple times to enable multiple type of metadata:

  • Location: The class name, file name, and line number of the log call.
  • Identity: The identity of the current thread principle.
  • ThreadName: The name of the thread that performed the log call.
  • UserName: The name of the current user.
  • Domain: The AppDomain friendly name.
  • LoggerName: The name of the log4net logger that created this log entry.
  • Label: The name of the log4net logging level that caused this log entry (e.g. "FATAL")

Custom labels

(Added via the AddCustomLabel method.)

<customLabel>
  <key value="<your key>" />
  <value value="<your value>" />
</customLabel>

Allows custom labels to be added to the metadata of log entries.

UsePatternWithinCustomLabels

<usePatternWithinCustomLabels value="true"/>

Defaults to false. Setting this to true enables PatternLayout use in custom labels. All the standard patterns documented for Log4Net PatternLayout are available. Custom pattern conversions are not possible.

ResourceType

<resourceType value="<your resource type>"/>

Allows the Monitored resources resource type to be specified; must be set to an entry on the Monitored Resource List.

If unset, the default value depends on the detected platform:

  • gae_app on Google App Engine (GAE).
  • gce_instance on Google Compute Engine (GCE).
  • global on all other platforms.

(See MonitoredResourceBuilder.cs for details)

ResourceLabel

<resourceLabel>
  <key value="<key>" />
  <value value="<value>" />
</resourceLabel>

If resourceType has been manually set, then resourceLabel is used to specify extra labels as shown in the Monitoring Resource Types list.

DisableResourceTypeDetection

<disableResourceTypeDetection value="true">

Defaults to false. Setting this to true disables automatic resourceType setting based on platform detection.

CredentialFile

<credentialFile value="path/to/credential.json" />

Specifies the path to a JSON file containing credentials.

CredentialJson

<credentialJson value="{ inline JSON credentials }" />

Specifies JSON credentials directly in the configuration. This can be useful if credentials are obtained out-of-band (e.g. from a key store) to avoid the data ever needing to be stored in the file system.

Complete configuration examples

So, for example, a complete configuration might look like this:

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="CloudLogger" type="Google.Cloud.Logging.Log4Net.GoogleStackdriverAppender,Google.Cloud.Logging.Log4Net">
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message" />
    </layout>
    <projectId value="myProject" />
    <logId value="myLog" />
    <withMetaData value="Location" />
    <withMetaData value="ThreadName" />
  </appender>
</log4net>

Configuration example: Translating Log4Net properties to Google Cloud Logging labels

If you wanted to transalate Log4Net properties to Google Cloud Logging labels, you can do as follows:

Create a log4net configuration file (log4net.xml):

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="CloudLogger" type="Google.Cloud.Logging.Log4Net.GoogleStackdriverAppender,Google.Cloud.Logging.Log4Net">
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message" />
    </layout>
    <projectId value="PROJECT_ID" />
    <logId value="LOG_ID" />
    <usePatternWithinCustomLabels value="true"/>
    <customLabel>
      <key value="label_from_property" />
      <value value="%property{property_for_label}" />
    </customLabel>
  </appender>
  <root>
    <level value="ALL" />
    <appender-ref ref="CloudLogger" />
  </root>
</log4net>

Edit the file replacing PROJECT_ID with your Google Cloud Project ID, and LOG_ID with an identifier for your application.

Use this file to configure log4net and then set Log4Net properties per scopes and log as normal. If a property referenced in a custom label doesn't have a value for a given entry, the value will be (null).

// Configure log4net to use Google Cloud Logging from the XML configuration file.
XmlConfigurator.Configure(LogManager.GetRepository(GetType().Assembly), new FileInfo("log4net.xml"));

// Retrieve a logger for this context.
ILog log = LogManager.GetLogger(typeof(Program));
// Set a property using one of the GlobaContext, ThreadContext or LogicalThreadContext classes.
LogicalThreadContext.Properties["property_for_label"] = "my label value";
// Log some information. This log entry will be sent to Google Cloud Logging with a label
// {"label_from_property", "my label value"}. The label key "label_from_property" is the one
// specified in configuration for a custom label that gets its value from the "property_for_label"
// property.
log.Info("An exciting log entry!");

// Flush buffered log entries before program exit.
// This is required because log entries are buffered locally before being sent to Google Cloud Logging.
// LogManager.Flush() only works in the full .NET framework (not in .NET Core):
bool flushCompleted = LogManager.Flush(10_000);
// On .NET Core, the specific repository needs to be flushed:
bool repositoryFlushCompleted = ((IFlushable)LogManager.GetRepository(GetType().Assembly)).Flush(10_000);

If executing on Google App Engine (GAE), Google Kubernetes Engine (GKE), or Google Compute Engine (GCE), then the <projectId value="PROJECT_ID" /> configuration setting can be omitted; it will be auto-detected from the platform at run-time.

Local queuing configuration

All log entries are queued locally before being sent to Google Cloud Logging. Only in-memory queuing is currently supported. Persistent on-disk queuing is not currently implemented.

The following in-memory options are available:

<MaxMemorySize value="<maximum number of bytes to queue>"/>

Restricts the amount of memory used for local queuing. If memory is exceeded then the oldest log entries will be discarded. A best-effort attempt will be made to log a warning to Google Cloud Logging that some log messages have been discarded. A value of 0 means there is no byte limit.

<MaxMemoryCount value="<maximum count of log messages to queue"/>

The maximum count of log messages that be ever be concurrently locally queued. If this maximum count is exceeded then the oldest log entries will be discarded. A best-effort attempt will be made to log a warning to Google Cloud Logging that some log messages have been discarded. A value of 0 means there is no count limit.

If MaxMemorySize and MaxMemoryCount are both specified, then messages will be discarded when the first limit is reached.

The default setting is to limit count to 1,000; with no limit on bytes.

Providing a JSON payload

Log entries in Google Cloud Logging either have a text payload or a JSON payload. By default, the Google.Cloud.Logging.Log4Net.GoogleStackdriverAppender uses a text payload. However, you can implement the IJsonLayout interface and configure the appender to use that. The JSON payload is expressed as a Protocol Buffers Struct message. If the IJsonLayout.Format method returns null from your layout, the appender will create a text payload as normal.

For example, here's a sample JSON layout implementation that demonstrates creating a payload with a mixture of values from execution time (the event message and exception) and from configuration (the "SampleConfiguration" value).

public class SampleJsonLayout : IJsonLayout, IOptionHandler
{
    public Struct _template;

    public int SampleConfigurationValue { get; set; }

    public void ActivateOptions()
    {
        _template = new Struct();
        _template.Fields["SampleConfiguration"] = Value.ForNumber(SampleConfigurationValue);
    }

    public Struct Format(LoggingEvent loggingEvent)
    {
        Struct ret = _template.Clone();
        ret.Fields["Message"] = Value.ForString(loggingEvent.RenderedMessage);
        if (loggingEvent.ExceptionObject != null)
        {
            ret.Fields["Exception"] = Value.ForString(loggingEvent.ExceptionObject.Message);
        }
        return ret;
    }
}

You would then configure the appender in XML like this:

<appender name="CloudLogger" type="Google.Cloud.Logging.Log4Net.GoogleStackdriverAppender,Google.Cloud.Logging.Log4Net">
  <!-- The IJsonLayout implementation used to create JSON payloads -->  
  <jsonLayout type="YourNamespace.SampleJsonLayout,YourAssemblyName">
    <sampleConfigurationValue value="10" />
  </jsonLayout>

  <!-- 
    - Other configuration as normal. The textlayout is used if the
    -  JSON layout returns null.
    -->

  <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message" />
  </layout>
  <projectId value="PROJECT_ID" />
  <logId value="LOG_ID" />
</appender>

Feedback...

Please file GitHub issues for bugs or questions.