Example #1
0
        private static List <DefaultDimension> GetDefaultDimensions(LoggerFactoryArguments arguments)
        {
            // This is a set of default dimensions used by all AP services:
            var fieldsProvider = arguments.TelemetryFieldsProvider;
            var result         = new List <DefaultDimension>
            {
                new ("Machine", fieldsProvider.MachineName),
                new ("ProcessName", AppDomain.CurrentDomain.FriendlyName),
                new ("Stamp", fieldsProvider.Stamp),
                new ("Ring", fieldsProvider.Ring),
                new ("Environment", fieldsProvider.APEnvironment),
                new ("Cluster", fieldsProvider.APCluster),
                new ("ServiceVersion", fieldsProvider.ServiceVersion),
            };

            return(result
                   // Filtering out nulls
                   .Where(d => !string.IsNullOrEmpty(d.Value))
                   .ToList());
        }
Example #2
0
        private static IStructuredLogger CreateNLogAdapter(OperationContext operationContext, LoggerFactoryArguments arguments)
        {
            Contract.RequiresNotNull(arguments.LoggingSettings);

            // This is done for performance. See: https://github.com/NLog/NLog/wiki/performance#configure-nlog-to-not-scan-for-assemblies
            NLog.Config.ConfigurationItemFactory.Default = new NLog.Config.ConfigurationItemFactory(typeof(NLog.ILogger).GetTypeInfo().Assembly);

            // This is needed for dependency ingestion. See: https://github.com/NLog/NLog/wiki/Dependency-injection-with-NLog
            // The issue is that we need to construct a log, which requires access to both our config and the host. It
            // seems too much to put it into the AzureBlobStorageLogTarget itself, so we do it here.
            var defaultConstructor = NLog.Config.ConfigurationItemFactory.Default.CreateInstance;

            NLog.Config.ConfigurationItemFactory.Default.CreateInstance = type =>
            {
                if (type == typeof(AzureBlobStorageLogTarget))
                {
                    var log    = CreateAzureBlobStorageLogAsync(operationContext, arguments).Result;
                    var target = new AzureBlobStorageLogTarget(log);
                    return(target);
                }

                return(defaultConstructor(type));
            };

            NLog.Targets.Target.Register <AzureBlobStorageLogTarget>(nameof(AzureBlobStorageLogTarget));

            // This is done in order to allow our logging configuration to access key telemetry information.
            var telemetryFieldsProvider = arguments.TelemetryFieldsProvider;

            NLog.LayoutRenderers.LayoutRenderer.Register("APEnvironment", _ => telemetryFieldsProvider.APEnvironment);
            NLog.LayoutRenderers.LayoutRenderer.Register("APCluster", _ => telemetryFieldsProvider.APCluster);
            NLog.LayoutRenderers.LayoutRenderer.Register("APMachineFunction", _ => telemetryFieldsProvider.APMachineFunction);
            NLog.LayoutRenderers.LayoutRenderer.Register("MachineName", _ => telemetryFieldsProvider.MachineName);
            NLog.LayoutRenderers.LayoutRenderer.Register("ServiceName", _ => telemetryFieldsProvider.ServiceName);
            NLog.LayoutRenderers.LayoutRenderer.Register("ServiceVersion", _ => telemetryFieldsProvider.ServiceVersion);
            NLog.LayoutRenderers.LayoutRenderer.Register("Stamp", _ => telemetryFieldsProvider.Stamp);
            NLog.LayoutRenderers.LayoutRenderer.Register("Ring", _ => telemetryFieldsProvider.Ring);
            NLog.LayoutRenderers.LayoutRenderer.Register("ConfigurationId", _ => telemetryFieldsProvider.ConfigurationId);
            NLog.LayoutRenderers.LayoutRenderer.Register("CacheVersion", _ => Utilities.Branding.Version);

            NLog.LayoutRenderers.LayoutRenderer.Register("Role", _ => GlobalInfoStorage.GetGlobalInfo(GlobalInfoKey.LocalLocationStoreRole));
            NLog.LayoutRenderers.LayoutRenderer.Register("BuildId", _ => GlobalInfoStorage.GetGlobalInfo(GlobalInfoKey.BuildId));

            // Follows ISO8601 without timezone specification.
            // See: https://kusto.azurewebsites.net/docs/query/scalar-data-types/datetime.html
            // See: https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings?view=netframework-4.8#the-round-trip-o-o-format-specifier
            var processStartTimeUtc = SystemClock.Instance.UtcNow.ToString("o", System.Globalization.CultureInfo.InvariantCulture);

            NLog.LayoutRenderers.LayoutRenderer.Register("ProcessStartTimeUtc", _ => processStartTimeUtc);

            var configurationContent = File.ReadAllText(arguments.LoggingSettings.NLogConfigurationPath);

            foreach (var replacement in arguments.LoggingSettings.NLogConfigurationReplacements)
            {
                configurationContent = configurationContent.Replace(replacement.Key, replacement.Value);
            }

            var textreader = new StringReader(configurationContent);
            var reader     = XmlReader.Create(textreader);

            var configuration = new NLog.Config.XmlLoggingConfiguration(reader, arguments.LoggingSettings.NLogConfigurationPath);

            return(new NLogAdapter(operationContext.TracingContext.Logger, configuration));
        }
Example #3
0
        /// <summary>
        ///     This method allows CASaaS to replace the host's logger for our own logger.
        /// </summary>
        /// <remarks>
        ///     Since we don't perform any kind of stats aggregation on our side (i.e. statsd, MDM, etc), we rely on
        ///     the host to do it. This is done through <see cref="MetricsAdapter"/>.
        ///
        ///     The situation with respect to shutdown is a little bit odd: we create a custom target, which holds some
        ///     managed resources that need to be released (because this release ensures any remaining logs will be
        ///     sent to Kusto).
        ///     NLog will make sure to dispose those resources when we shut it down, but we may actually return the
        ///     host's logger, in which case we don't consider that we own it, because clean up may happen on whatever
        ///     code is actually using us, so we don't want to dispose in that case.
        /// </remarks>
        public static (ILogger Logger, IDisposable DisposableToken) CreateReplacementLogger(LoggerFactoryArguments arguments)
        {
            Contract.RequiresNotNull(arguments.TelemetryFieldsProvider);

            var logger = arguments.Logger;

            var loggingSettings = arguments.LoggingSettings;

            if (string.IsNullOrEmpty(loggingSettings?.NLogConfigurationPath))
            {
                return(logger, null);
            }

            // This context is associated to the host's logger. In this way, we can make sure that if we have any
            // issues with our logging, we can always go and read the host's logs to figure out what's going on.
            var context          = new Context(logger);
            var operationContext = new OperationContext(context);

            context.Info($"Replacing cache logger for NLog-based implementation using configuration file at `{loggingSettings.NLogConfigurationPath}`");

            try
            {
                var nLogAdapter = CreateNLogAdapter(operationContext, arguments);
                if (arguments.Logger is IOperationLogger operationLogger)
                {
                    // NOTE(jubayard): the MetricsAdapter doesn't own the loggers, and hence won't dispose them. This
                    // means we don't change the disposableToken.
                    var wrapper = new MetricsAdapter(nLogAdapter, operationLogger);
                    return(wrapper, nLogAdapter);
                }

                return(nLogAdapter, nLogAdapter);
            }
            catch (Exception e)
            {
                context.Error($"Failed to instantiate NLog-based logger with error: {e}");
                return(logger, null);
            }
        }
Example #4
0
        private static async Task <AzureBlobStorageLog> CreateAzureBlobStorageLogAsync(OperationContext operationContext, LoggerFactoryArguments arguments)
        {
            var configuration = arguments.LoggingSettings.Configuration;

            Contract.AssertNotNull(configuration);

            // There is a big issue here: on the one hand, we'd like to be able to configure everything from the XML
            // instead of our JSON configuration, simply because the XML is self-contained. On the other hand, the XML
            // will likely be shared across all stamps, so there's no "stamp-specific" configuration in there. That
            // means all stamp-level configuration must be done through the JSON.

            AzureBlobStorageCredentials credentials = await arguments.SecretsProvider.GetBlobCredentialsAsync(
                configuration.SecretName,
                configuration.UseSasTokens,
                operationContext.Token);

            var azureBlobStorageLogConfiguration = ToInternalConfiguration(configuration);

            var azureBlobStorageLog = new AzureBlobStorageLog(
                configuration: azureBlobStorageLogConfiguration,
                context: operationContext,
                clock: SystemClock.Instance,
                fileSystem: new PassThroughFileSystem(),
                telemetryFieldsProvider: arguments.TelemetryFieldsProvider,
                credentials: credentials,
                additionalBlobMetadata: null);

            await azureBlobStorageLog.StartupAsync().ThrowIfFailure();

            return(azureBlobStorageLog);
        }
Example #5
0
        /// <summary>
        ///     This method allows CASaaS to replace the host's logger for our own logger.
        /// </summary>
        /// <remarks>
        ///     Since we don't perform any kind of stats aggregation on our side (i.e. statsd, MDM, etc), we rely on
        ///     the host to do it. This is done through <see cref="MetricsAdapter"/>.
        ///
        ///     The situation with respect to shutdown is a little bit odd: we create a custom target, which holds some
        ///     managed resources that need to be released (because this release ensures any remaining logs will be
        ///     sent to Kusto).
        ///     NLog will make sure to dispose those resources when we shut it down, but we may actually return the
        ///     host's logger, in which case we don't consider that we own it, because clean up may happen on whatever
        ///     code is actually using us, so we don't want to dispose in that case.
        /// </remarks>
        public static (ILogger Logger, IDisposable DisposableToken) CreateReplacementLogger(LoggerFactoryArguments arguments)
        {
            var logger = arguments.Logger;

            var loggingSettings = arguments.LoggingSettings;

            if (string.IsNullOrEmpty(loggingSettings?.NLogConfigurationPath))
            {
                return(logger, null);
            }

            // This context is associated to the host's logger. In this way, we can make sure that if we have any
            // issues with our logging, we can always go and read the host's logs to figure out what's going on.
            var context          = new Context(logger);
            var operationContext = new OperationContext(context);

            Tracer.Info(context, $"Replacing cache logger for NLog-based implementation using configuration file at `{loggingSettings.NLogConfigurationPath}`");

            try
            {
                var nLogAdapter = CreateNLogAdapter(operationContext, arguments);
                context = new Context(nLogAdapter);
                if (arguments.Logger is IOperationLogger operationLogger)
                {
                    // NOTE(jubayard): the MetricsAdapter doesn't own the loggers, and hence won't dispose them. This
                    // means we don't change the disposableToken.
                    Tracer.Debug(context, "Creating MetricsAdapter with an existing 'operationLogger'.");
                    var wrapper = new MetricsAdapter(nLogAdapter, operationLogger);
                    return(wrapper, nLogAdapter);
                }
                // The current implementation now supports the mdm metrics as well.
                if (!string.IsNullOrEmpty(arguments.LoggingSettings.MdmAccountName))
                {
                    Tracer.Debug(context, "Creating MetricsLogger with an in-proc MdmOperationLogger.");
                    operationLogger = MdmOperationLogger.Create(context, arguments.LoggingSettings.MdmAccountName, GetDefaultDimensions(arguments));
                    var wrapper = new MetricsAdapter(nLogAdapter, operationLogger);
                    return(wrapper, nLogAdapter);
                }
                return(nLogAdapter, nLogAdapter);
            }
            catch (Exception e)
            {
                Tracer.Error(context, $"Failed to instantiate NLog-based logger with error: {e}");
                return(logger, null);
            }
        }