Example #1
0
        /// <summary>
        /// Process the input log information to remove all unauthorized information
        /// </summary>
        /// <param name="className">usually the class that is calling the method</param>
        /// <param name="method">usually denotes method calling this method</param>
        /// <param name="message">informational message</param>
        /// <param name="logCode">error code, usually empty</param>
        /// <param name="corpusPath">usually denotes corpus path of document</param>
        /// <param name="correlationId">corpus correlation id</param>
        /// <param name="apiCorrelationId">method correlation id</param>
        /// <param name="appId">app id assigned by user</param>
        /// <returns>A complete log entry</returns>
        private string ProcessLogEntry(string timestamp, string className, string method, string message,
                                       CdmLogCode logCode, string corpusPath, string correlationId, Guid apiCorrelationId, string appId)
        {
            // Remove user created contents
            if (this.config.IngestAtLevel == EnvironmentType.PROD || this.config.IngestAtLevel == EnvironmentType.TEST)
            {
                corpusPath = null;
            }

            if (message == null)
            {
                message = "";
            }

            // Remove all commas from message to ensure the correct syntax of Kusto query
            message = message.Replace(",", ";");
            string code = logCode.ToString();

            // Additional properties associated with the log
            Dictionary <string, string> property = new Dictionary <string, string>();

            property.Add("Environment", this.config.IngestAtLevel.ToString());
            property.Add("SDKLanguage", "CSharp");
            property.Add("Region", this.config.Region);
            string propertyJson = SerializeDictionary(property);

            string entry = $"{timestamp},{className},{method},{message},{code},{corpusPath},{correlationId},{apiCorrelationId},{appId},{propertyJson}\n";

            return(entry);
        }
Example #2
0
        /// <summary>
        /// Enqueue the request queue with the information to be logged
        /// </summary>
        /// <param name="timestamp">The log timestamp</param>
        /// <param name="level">Logging status level</param>
        /// <param name="className">Usually the class that is calling the method</param>
        /// <param name="method">Usually denotes method calling this method</param>
        /// <param name="corpusPath">Usually denotes corpus path of document</param>
        /// <param name="message">Informational message</param>
        /// <param name="requireIngestion">(Optional) Whether the log needs to be ingested</param>
        /// <param name="code">(Optional) Error or warning code</param>
        public void AddToIngestionQueue(string timestamp, CdmStatusLevel level,
                                        string className, string method, string corpusPath, string message,
                                        bool requireIngestion = false, CdmLogCode code = CdmLogCode.None)
        {
            // Check if the Kusto config and the concurrent queue has been initialized
            if (this.config == null || this.requestQueue == null)
            {
                return;
            }

            // Not ingest logs from telemetry client to avoid cycling
            if (className == nameof(TelemetryKustoClient))
            {
                return;
            }

            // If ingestion is not required and the level is Progress
            if (level == CdmStatusLevel.Progress && !requireIngestion)
            {
                // If the execution time needs to be logged
                if (logExecTimeMethods.Contains(method))
                {
                    // Check if the log contains execution time info
                    string execTimeMessage = "Leaving scope. Time elapsed:";

                    // Skip if the log is not for execution time
                    if (!message.StartsWith(execTimeMessage))
                    {
                        return;
                    }
                }

                // Skip if the method execution time doesn't need to be logged
                else
                {
                    return;
                }
            }

            // Configured in case no user-created content can be ingested into Kusto due to compliance issue
            // Note: The RemoveUserContent property could be deleted in the if the compliance issue gets resolved
            if (this.config.RemoveUserContent)
            {
                corpusPath = null;
                if (level == CdmStatusLevel.Warning || level == CdmStatusLevel.Error)
                {
                    message = null;
                }
            }

            string logEntry = ProcessLogEntry(timestamp, className, method, message, code, corpusPath,
                                              this.ctx.CorrelationId, this.ctx.Events.ApiCorrelationId, this.ctx.Corpus.AppId);

            // Add the status level and log entry to the queue to be ingested
            this.requestQueue.Enqueue(new Tuple <CdmStatusLevel, string>(level, logEntry));
        }
Example #3
0
        /// <summary>
        /// Loads the string from resource file for particular enum and inserts arguments in it.
        /// </summary>
        /// <param name="code">The code, denotes the code enum for a message.</param>
        /// <param name="args">The args, denotes the arguments inserts into the messages.</param>
        private static string GetMessagefromResourceFile(CdmLogCode code, params string[] args)
        {
            StringBuilder builder = new StringBuilder(resManager.GetString(code.ToString()));

            int i = 0;

            foreach (string x in args)
            {
                string str = "{" + i + "}";
                builder.Replace(str, x);
                i++;
            }

            return(builder.ToString());
        }
Example #4
0
        /// <summary>
        /// Asserts in logcode, if expected log code is not in log codes recorded list.
        /// </summary>
        /// <param name="corpus">The corpus object.</param>
        /// <param name="expectedcode">The expectedcode cdmlogcode.</param>
        public static void AssertCdmLogCodeEquality(CdmCorpusDefinition corpus, CdmLogCode expectedCode)
        {
            bool toAssert = false;

            corpus.Ctx.Events.ForEach(logEntry =>
            {
                if (((expectedCode.ToString().StartsWith("Warn") && logEntry["level"].Equals(CdmStatusLevel.Warning.ToString())) ||
                     (expectedCode.ToString().StartsWith("Err") && logEntry["level"].Equals(CdmStatusLevel.Error.ToString()))) &&
                    logEntry["code"].Equals(expectedCode.ToString()))
                {
                    toAssert = true;
                }
            });
            Assert.IsTrue(toAssert, $"The recorded log events should have contained message with log code {expectedCode} of appropriate level");
        }
Example #5
0
        /// <summary>
        /// Log to the specified status level by using the status event on the corpus context (if it exists) or to the default logger.
        /// The log level, className, message and path values are also added as part of a new entry to the log recorder.
        /// </summary>
        /// <param name="level">The status level to log to.</param>
        /// <param name="ctx">The CDM corpus context.</param>
        /// <param name="className">The className, usually the class that is calling the method.</param>
        /// <param name="message">The message.</param>
        /// <param name="method">The path, usually denotes the class and method calling this method.</param>
        /// <param name="defaultStatusEvent">The default status event (log using the default logger).</param>
        /// <param name="code">The code(optional), denotes the code enum for a message.</param>
        private static void Log(CdmStatusLevel level, CdmCorpusContext ctx, string className, string message, string method, Action <string> defaultStatusEvent, string corpusPath, CdmLogCode code = CdmLogCode.None)
        {
            // Store a record of the event.
            // Save some dict init and string formatting cycles by checking
            // whether the recording is actually enabled.
            if (ctx.Events.IsRecording)
            {
                var theEvent = new Dictionary <string, string>
                {
                    { "timestamp", TimeUtils.GetFormattedDateString(DateTimeOffset.UtcNow) },
                    { "level", level.ToString() },
                    { "class", className },
                    { "message", message },
                    { "method", method }
                };

                if (level == CdmStatusLevel.Error || level == CdmStatusLevel.Warning)
                {
                    theEvent.Add("code", code.ToString());
                }

                if (ctx.CorrelationId != null)
                {
                    theEvent.Add("correlationId", ctx.CorrelationId);
                }

                if (corpusPath != null)
                {
                    theEvent.Add("corpuspath", corpusPath);
                }

                ctx.Events.Add(theEvent);
            }

            string formattedMessage = FormatMessage(className, message, method, ctx.CorrelationId, corpusPath);

            if (ctx.StatusEvent != null)
            {
                ctx.StatusEvent.Invoke(level, formattedMessage);
            }
            else
            {
                defaultStatusEvent(formattedMessage);
            }
        }
Example #6
0
        /// <summary>
        /// Log to ERROR level.This is extension to Error function for new logging.
        /// </summary>
        /// <param name="ctx">The CDM corpus context.</param>
        /// <param name="className">The className, usually the class that is calling the method.</param>
        /// <param name="method">The path, usually denotes the method calling this method.</param>
        /// <param name="corpusPath">The corpusPath, usually denotes corpus path of document.</param>
        /// <param name="code">The code, denotes the code enum for a message.</param>
        /// <param name="args">The args, denotes the arguments inserted into the messages.</param>
        public static void Error(CdmCorpusContext ctx, string className, string method, string corpusPath, CdmLogCode code, params string[] args)
        {
            if (CdmStatusLevel.Error >= ctx.ReportAtLevel)
            {
                // Get message from resource for the code enum.
                string message = GetMessagefromResourceFile(code, args);

                Log(CdmStatusLevel.Error, ctx, className, message, method, DefaultLogger.Error, corpusPath, code);
            }
        }
Example #7
0
        /// <summary>
        /// Log to ERROR level.This is extension to Error function for new logging.
        /// </summary>
        /// <param name="ctx">The CDM corpus context.</param>
        /// <param name="className">The className, usually the class that is calling the method.</param>
        /// <param name="method">The path, usually denotes the method calling this method.</param>
        /// <param name="corpusPath">The corpusPath, usually denotes corpus path of document.</param>
        /// <param name="code">The code, denotes the code enum for a message.</param>
        /// <param name="args">The args, denotes the arguments inserted into the messages.</param>
        public static void Error(CdmCorpusContext ctx, string className, string method, string corpusPath, CdmLogCode code, params string[] args)
        {
            // Get message from resource for the code enum.
            string message = GetMessageFromResourceFile(code, args);

            Log(CdmStatusLevel.Error, ctx, className, message, method, Console.Error.WriteLine, corpusPath, code, true);
        }
Example #8
0
        /// <summary>
        /// Log to the specified status level by using the status event on the corpus context (if it exists) or to the default logger.
        /// The log level, className, message and path values are also added as part of a new entry to the log recorder.
        /// </summary>
        /// <param name="level">The status level to log to.</param>
        /// <param name="ctx">The CDM corpus context.</param>
        /// <param name="className">The className, usually the class that is calling the method.</param>
        /// <param name="message">The message.</param>
        /// <param name="method">The path, usually denotes the class and method calling this method.</param>
        /// <param name="defaultStatusEvent">The default status event (log using the default logger).</param>
        /// <param name="code">The code(optional), denotes the code enum for a message.</param>
        private static void Log(CdmStatusLevel level, CdmCorpusContext ctx, string className, string message, string method,
                                Action <string> defaultStatusEvent, string corpusPath, CdmLogCode code = CdmLogCode.None, bool ingestTelemetry = false)
        {
            if (ctx.SuppressedLogCodes.Contains(code))
            {
                return;
            }

            // Store a record of the event.
            // Save some dict init and string formatting cycles by checking
            // whether the recording is actually enabled.
            if (level >= ctx.ReportAtLevel)
            {
                string timestamp = TimeUtils.GetFormattedDateString(DateTimeOffset.UtcNow);

                // Store a record of the event.
                // Save some dict init and string formatting cycles by checking
                // whether the recording is actually enabled.
                if (ctx.Events.IsRecording)
                {
                    var theEvent = new Dictionary <string, string>
                    {
                        { "timestamp", TimeUtils.GetFormattedDateString(DateTimeOffset.UtcNow) },
                        { "level", level.ToString() },
                        { "class", className },
                        { "message", message },
                        { "method", method }
                    };

                    if (level == CdmStatusLevel.Error || level == CdmStatusLevel.Warning)
                    {
                        theEvent.Add("code", code.ToString());
                    }

                    if (ctx.CorrelationId != null)
                    {
                        theEvent.Add("cid", ctx.CorrelationId);
                    }

                    if (corpusPath != null)
                    {
                        theEvent.Add("path", corpusPath);
                    }

                    ctx.Events.Add(theEvent);
                }

                string formattedMessage = FormatMessage(className, message, method, ctx.CorrelationId, corpusPath);

                if (ctx.StatusEvent != null)
                {
                    ctx.StatusEvent.Invoke(level, formattedMessage);
                }
                else
                {
                    defaultStatusEvent(formattedMessage);
                }

                // Ingest the logs into telemetry database
                if (ctx.Corpus.TelemetryClient != null)
                {
                    ctx.Corpus.TelemetryClient.AddToIngestionQueue(timestamp, level, className, method, corpusPath, message, ingestTelemetry, code);
                }
            }
        }