//--- Extension Methods --- /// <summary> /// Log a debugging message. This message will only appear in the log and will not be forwarded to an error aggregator. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="format">The message format string. If not arguments are supplied, the message format string will be printed as a plain string.</param> /// <param name="arguments">Optional arguments for the message string.</param> /// <seealso cref="LambdaLogLevel"/> public static void LogDebug(this ILambdaSharpLogger logger, string format, params object[] arguments) { if (logger.DebugLoggingEnabled) { logger.Log(LambdaLogLevel.DEBUG, exception: null, format: format, arguments: arguments); } }
//--- Constructors --- public UnzipLogic(ILambdaSharpLogger logger, string manifestBucket, IAmazonS3 s3Client) { _logger = logger; _manifestBucket = manifestBucket; _s3Client = new AmazonS3Client(); _transferUtility = new TransferUtility(_s3Client); }
/// <summary> /// Log a CloudWatch metric. The metric is picked up by CloudWatch logs and automatically ingested as a CloudWatch metric. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="name">Metric name.</param> /// <param name="value">Metric value.</param> /// <param name="unit">Metric unit.</param> /// <param name="dimensionNames">Metric dimensions as comma-separated list (e.g. [ "A", "A,B" ]).</param> /// <param name="dimensionValues">Dictionary of dimesion name-value pairs.</param> public static void LogMetric( this ILambdaSharpLogger logger, string name, double value, LambdaMetricUnit unit, IEnumerable <string> dimensionNames, Dictionary <string, string> dimensionValues ) => logger.LogMetric(new[] { new LambdaMetric(name, value, unit) }, dimensionNames, dimensionValues);
//--- Constructors --- public LambdaRobotsBotClient(string botId, string lambdaArn, TimeSpan requestTimeout, IAmazonLambda lambdaClient = null, ILambdaSharpLogger logger = null) { _botId = botId ?? throw new ArgumentNullException(nameof(botId)); _lambdaArn = lambdaArn ?? throw new ArgumentNullException(nameof(lambdaArn)); _requestTimeout = requestTimeout; _lambdaClient = lambdaClient ?? new AmazonLambdaClient(); _logger = logger; }
/// <summary> /// Send a CloudWatch event with optional event details and resources it applies to. This event is forwarded to the configured EventBridge. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="source">Name of the event source.</param> /// <param name="detailType">Free-form string used to decide what fields to expect in the event detail.</param> /// <param name="detail">Data-structure to serialize as a JSON string. If value is already a <code>string</code>, it is sent as-is. There is no other schema imposed. The data-structure may contain fields and nested subobjects.</param> /// <param name="resources">Optional AWS or custom resources, identified by unique identifier (e.g. ARN), which the event primarily concerns. Any number, including zero, may be present.</param> public static void LogEvent <T>(this ILambdaSharpLogger logger, string source, string detailType, T detail, IEnumerable <string> resources = null) { // augment event resources with LambdaSharp specific resources var lambdaResources = new List <string>(); if (resources != null) { lambdaResources.AddRange(resources); } if (logger.Info.ModuleId != null) { lambdaResources.Add($"lambdasharp:stack:{logger.Info.ModuleId}"); } var moduleFullName = logger.Info.GetModuleFullName(); if (moduleFullName != null) { lambdaResources.Add($"lambdasharp:module:{moduleFullName}"); } if (logger.Info.DeploymentTier != null) { lambdaResources.Add($"lambdasharp:tier:{logger.Info.DeploymentTier}"); } if (logger.Info.ModuleInfo != null) { lambdaResources.Add($"lambdasharp:moduleinfo:{logger.Info.ModuleInfo}"); } var moduleOrigin = logger.Info.GetModuleOrigin(); if (moduleOrigin != null) { lambdaResources.Add($"lambdasharp:origin:{moduleOrigin}"); } if (logger.Info.AppId != null) { lambdaResources.Add($"lambdasharp:app:{logger.Info.AppId}"); } // create event record for logging var eventRecord = new LambdaEventRecord { EventBus = "default", Source = source, DetailType = detailType, Detail = (detail is string detailText) ? detailText : JsonSerializer.Serialize(detail, JsonSerializerOptions), Resources = lambdaResources }; eventRecord.SetTime(DateTimeOffset.UtcNow); logger.LogRecord(eventRecord); } }
//--- Extension Methods --- /// <summary> /// Send a CloudWatch event with optional event details and resources it applies to. This event is forwarded to the configured EventBridge. The 'detail-type' property is set to the full type name of the detail value. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="source">Name of the event source.</param> /// <param name="detail">Data-structure to serialize as a JSON string. If value is already a <code>string</code>, it is sent as-is. There is no other schema imposed. The data-structure may contain fields and nested subobjects.</param> /// <param name="resources">Optional AWS or custom resources, identified by unique identifier (e.g. ARN), which the event primarily concerns. Any number, including zero, may be present.</param> public static void LogEvent <T>(this ILambdaSharpLogger logger, string source, T detail, IEnumerable <string> resources = null) => LogEvent <T>(logger, source, typeof(T).FullName, detail, resources);
/// <summary> /// Log a CloudWatch metric. The metric is picked up by CloudWatch logs and automatically ingested as a CloudWatch metric. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="metrics">Enumeration of metrics, including their name, value, and unit.</param> /// <param name="dimensionNames">Metric dimensions as comma-separated list (e.g. [ "A", "A,B" ]).</param> /// <param name="dimensionValues">Dictionary of dimesion name-value pairs.</param> public static void LogMetric( this ILambdaSharpLogger logger, IEnumerable <LambdaMetric> metrics, IEnumerable <string> dimensionNames, Dictionary <string, string> dimensionValues ) { if (!metrics.Any()) { return; } IEnumerable <string> newDimensionNames; Dictionary <string, string> newDimensionValues; if (logger.Info.ModuleId != null) { if (logger.Info.FunctionName != null) { // dimension the metric by 'Stack' and 'Function' newDimensionNames = dimensionNames.Union(new[] { "Stack", "Stack,Function" }).Distinct().ToList(); newDimensionValues = new Dictionary <string, string>(dimensionValues) { ["Stack"] = logger.Info.ModuleId, ["Function"] = logger.Info.FunctionName }; } else if (logger.Info.AppName != null) { // dimension the metric by 'Stack' and 'App' newDimensionNames = dimensionNames.Union(new[] { "Stack", "Stack,App" }).Distinct().ToList(); newDimensionValues = new Dictionary <string, string>(dimensionValues) { ["Stack"] = logger.Info.ModuleId, ["App"] = logger.Info.AppName }; } else { newDimensionNames = new List <string>(); newDimensionValues = new Dictionary <string, string>(dimensionValues); } } else { if (logger.Info.FunctionName != null) { // dimension the metric by 'Function' only newDimensionNames = dimensionNames.Union(new[] { "Function" }).Distinct().ToList(); newDimensionValues = new Dictionary <string, string>(dimensionValues) { ["Function"] = logger.Info.FunctionName }; } else if (logger.Info.AppName != null) { // dimension the metric by 'App' only newDimensionNames = dimensionNames.Union(new[] { "App" }).Distinct().ToList(); newDimensionValues = new Dictionary <string, string>(dimensionValues) { ["App"] = logger.Info.AppName }; } else { newDimensionNames = new List <string>(); newDimensionValues = new Dictionary <string, string>(dimensionValues); } } // add git sha and git branch as extra metadata when available if (logger.Info.GitSha != null) { newDimensionValues["GitSha"] = logger.Info.GitSha; } if (logger.Info.GitBranch != null) { newDimensionValues["GitBranch"] = logger.Info.GitBranch; } logger.LogMetric($"Module:{logger.Info.GetModuleFullName()}", metrics, newDimensionNames, newDimensionValues); }
/// <summary> /// Log a CloudWatch metric. The metric is picked up by CloudWatch logs and automatically ingested as a CloudWatch metric. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="metrics">Enumeration of metrics, including their name, value, and unit.</param> public static void LogMetric(this ILambdaSharpLogger logger, IEnumerable <LambdaMetric> metrics) => logger.LogMetric(metrics, Array.Empty <string>(), new Dictionary <string, string>());
//--- Extension Methods --- /// <summary> /// Log a CloudWatch metric. The metric is picked up by CloudWatch logs and automatically ingested as a CloudWatch metric. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="name">Metric name.</param> /// <param name="value">Metric value.</param> /// <param name="unit">Metric unit.</param> public static void LogMetric( this ILambdaSharpLogger logger, string name, double value, LambdaMetricUnit unit ) => logger.LogMetric(new[] { new LambdaMetric(name, value, unit) });
/// <summary> /// Log a CloudWatch metric. The metric is picked up by CloudWatch logs and automatically ingested as a CloudWatch metric. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="namespace">Metric namespace.</param> /// <param name="metrics">Enumeration of metrics, including their name, value, and unit.</param> /// <param name="dimensionNames">Metric dimensions as comma-separated list (e.g. [ "A", "A,B" ]).</param> /// <param name="dimensionValues">Dictionary of dimesion name-value pairs.</param> public static void LogMetric( this ILambdaSharpLogger logger, string @namespace, IEnumerable <LambdaMetric> metrics, IEnumerable <string> dimensionNames, Dictionary <string, string> dimensionValues ) { if (!metrics.Any()) { // nothing to do return; } if (dimensionNames.Count() > 9) { throw new ArgumentException("metric cannot exceed 9 dimensions", nameof(dimensionNames)); } var targets = new Dictionary <string, object>(); // validate and process metrics var index = 0; foreach (var metric in metrics) { if (string.IsNullOrEmpty(metric.Name)) { throw new ArgumentException($"metric name cannot be empty (index {index})"); } AssertNotReservedName(metric.Name, "metric"); if (double.IsNaN(metric.Value) || double.IsNegativeInfinity(metric.Value) || double.IsPositiveInfinity(metric.Value)) { // these values are rejected by CloudWatch metrics throw new ArgumentException($"metric '{metric.Name}' has an out-of-range value"); } if (!targets.TryAdd(metric.Name, metric.Value)) { throw new ArgumentException($"conflicting metric name: {metric.Name}"); } ++index; } // validate and process dimension values index = 0; foreach (var dimensionValue in dimensionValues) { if (string.IsNullOrEmpty(dimensionValue.Key)) { throw new ArgumentException($"dimension name cannot be empty (index {index})"); } if (string.IsNullOrEmpty(dimensionValue.Value)) { throw new ArgumentException($"dimension value cannot be empty (index {index})"); } AssertNotReservedName(dimensionValue.Key, "dimension"); if (!targets.TryAdd(dimensionValue.Key, dimensionValue.Value)) { throw new ArgumentException($"conflicting dimension name: {dimensionValue.Key}"); } ++index; } // validate and process metric dimensions var metricDimensions = dimensionNames .Select(dimension => dimension .Split(',') .Select(dim => dim.Trim()) .ToList() ) .ToList(); foreach (var metricDimension in metricDimensions.SelectMany(dimension => dimension)) { if (!dimensionValues.ContainsKey(metricDimension)) { throw new ArgumentException($"missing dimension value: {metricDimension}"); } } // create embedded metrics data-structure: var record = new LambdaMetricsRecord { Aws = new EmbeddedCloudWatchMetrics { CloudWatchMetrics = { new CloudWatchMetrics { Namespace = @namespace, Dimensions = metricDimensions, Metrics = metrics.Select(metric => new CloudWatchMetricValue { Name = metric.Name, Unit = ConvertUnit(metric.Unit) }).ToList() } } }, TargetMembers = targets }; // log metric record logger.LogRecord(record); // local functions void AssertNotReservedName(string name, string parameterType) { switch (name) { case "_aws": case "Source": case "Version": throw new ArgumentException($"{parameterType} name cannot be named '{name}'"); default: break; } } string ConvertUnit(LambdaMetricUnit unit) { switch (unit) { // these enum names need to be mapped to their correct CloudWatch metrics unit counterpart case LambdaMetricUnit.BytesPerSecond: return("Bytes/Second"); case LambdaMetricUnit.KilobytesPerSecond: return("Kilobytes/Second"); case LambdaMetricUnit.MegabytesPerSecond: return("Megabytes/Second"); case LambdaMetricUnit.GigabytesPerSecond: return("Gigabytes/Second"); case LambdaMetricUnit.TerabytesPerSecond: return("Terabytes/Second"); case LambdaMetricUnit.BitsPerSecond: return("Bits/Second"); case LambdaMetricUnit.KilobitsPerSecond: return("Kilobits/Second"); case LambdaMetricUnit.MegabitsPerSecond: return("Megabits/Second"); case LambdaMetricUnit.GigabitsPerSecond: return("Gigabits/Second"); case LambdaMetricUnit.TerabitsPerSecond: return("Terabits/Second"); case LambdaMetricUnit.CountPerSecond: return("Count/Second"); // the remaining enums are good as is default: return(unit.ToString()); } } }
/// <summary> /// Log an exception as a warning. This message will be reported if an error aggregator is configured for the <c>LambdaSharp.Core</c> module. /// </summary> /// <remarks> /// Only use this method when the exception has no operational impact. /// Otherwise, either use <see cref="LogError(ILambdaSharpLogger,Exception)"/>. /// </remarks> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="exception">The exception to log. The exception is logged with its message, stacktrace, and any nested exceptions.</param> /// <seealso cref="LambdaLogLevel"/> public static void LogErrorAsWarning(this ILambdaSharpLogger logger, Exception exception) => logger.Log(LambdaLogLevel.WARNING, exception, exception.Message, Array.Empty <object>());
//--- Constructors --- public Logic(ILambdaSharpLogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); }
/// <summary> /// Log an exception as an information message. This message will only appear in the log and will not be forwarded to an error aggregator. /// </summary> /// <remarks> /// Only use this method when the exception has no operational impact. /// Otherwise, either use <see cref="LogError(ILambdaSharpLogger,Exception)"/> or <see cref="LogErrorAsWarning(ILambdaSharpLogger,Exception)"/>. /// </remarks> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="exception">The exception to log. The exception is logged with its message, stacktrace, and any nested exceptions.</param> /// <seealso cref="LambdaLogLevel"/> public static void LogErrorAsInfo(this ILambdaSharpLogger logger, Exception exception) => logger.Log(LambdaLogLevel.INFO, exception, exception.Message, Array.Empty <object>());
/// <summary> /// Log an exception with a custom message as an error. This message will be reported if an error aggregator is configured for the <c>LambdaSharp.Core</c> module. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="exception">The exception to log. The exception is logged with its message, stacktrace, and any nested exceptions.</param> /// <param name="format">Optional message to use instead of <c>Exception.Message</c>. This parameter can be <c>null</c>.</param> /// <param name="arguments">Optional arguments for the <c>format</c> parameter.</param> /// <seealso cref="LambdaLogLevel"/> public static void LogError(this ILambdaSharpLogger logger, Exception exception, string format, params object[] arguments) => logger.Log(LambdaLogLevel.ERROR, exception, format, arguments);
/// <summary> /// Log a warning message. This message will be reported if an error aggregator is configured for the <c>LambdaSharp.Core</c> module. /// </summary> /// <param name="logger">The <see cref="ILambdaSharpLogger"/> instance to use.</param> /// <param name="format">The message format string. If not arguments are supplied, the message format string will be printed as a plain string.</param> /// <param name="arguments">Optional arguments for the message string.</param> /// <seealso cref="LambdaLogLevel"/> public static void LogWarn(this ILambdaSharpLogger logger, string format, params object[] arguments) => logger.Log(LambdaLogLevel.WARNING, exception: null, format: format, arguments: arguments);
//--- Constructors --- public WriteJsonLogic(ILambdaSharpLogger logger, IAmazonS3 s3Client, ILambdaSerializer jsonSerializer) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _s3Client = s3Client ?? throw new ArgumentNullException(nameof(s3Client)); _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); }
//--- Constructors --- public EmptyBucketLogic(ILambdaSharpLogger logger, IAmazonS3 s3Client) { _logger = logger; _s3Client = s3Client; }