Warning
Version 1.x.x will continue to be supported until end of July 2025 for critical bug fixes and security updates in very exceptional cases where you cannot update to v2, but no new features will be added to this version.
We recommend you upgrade to the latest version.
The latest version is available at Logging v2.
The logging utility provides a Lambda optimized logger with output structured as JSON.
Key features¶Powertools for AWS Lambda (.NET) are available as NuGet packages. You can install the packages from NuGet Gallery or from Visual Studio editor by searching AWS.Lambda.Powertools*
to see various utilities available.
AWS.Lambda.Powertools.Logging:
dotnet add package AWS.Lambda.Powertools.Logging --version 1.6.5
Info
AOT Support If loooking for AOT specific configurations navigate to the AOT section
Logging requires two settings:
Setting Description Environment variable Attribute parameter Service Sets Service key that will be present across all log statementsPOWERTOOLS_SERVICE_NAME
Service
Logging level Sets how verbose Logger should be (Information, by default) POWERTOOLS_LOG_LEVEL
LogLevel
Service Property Priority Resolution¶
The root level Service property now correctly follows this priority order:
You can override log level by setting POWERTOOLS_LOG_LEVEL
environment variable in the AWS SAM template.
You can also explicitly set a service name via POWERTOOLS_SERVICE_NAME
environment variable. This sets Service key that will be present across all log statements.
Here is an example using the AWS SAM Globals section.
template.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
Example project for Powertools for AWS Lambda (.NET) Logging utility
Globals:
Function:
Timeout: 10
Environment:
Variables:
POWERTOOLS_SERVICE_NAME: powertools-dotnet-logging-sample
POWERTOOLS_LOG_LEVEL: Debug
POWERTOOLS_LOGGER_LOG_EVENT: true
POWERTOOLS_LOGGER_CASE: PascalCase # Allowed values are: CamelCase, PascalCase and SnakeCase
POWERTOOLS_LOGGER_SAMPLE_RATE: 0
Full list of environment variables¶ Environment variable Description Default POWERTOOLS_SERVICE_NAME Sets service name used for tracing namespace, metrics dimension and structured logging "service_undefined"
POWERTOOLS_LOG_LEVEL Sets logging level Information
POWERTOOLS_LOGGER_CASE Override the default casing for log keys SnakeCase
POWERTOOLS_LOGGER_LOG_EVENT Logs incoming event false
POWERTOOLS_LOGGER_SAMPLE_RATE Debug log sampling 0
Using AWS Lambda Advanced Logging Controls (ALC)¶
When is it useful?
When you want to set a logging policy to drop informational or verbose logs for one or all AWS Lambda functions, regardless of runtime and logger used.
With AWS Lambda Advanced Logging Controls (ALC), you can enforce a minimum log level that Lambda will accept from your application code.
When enabled, you should keep Logger
and ALC log level in sync to avoid data loss.
When using AWS Lambda Advanced Logging Controls (ALC)
PascalCase
Level
property name will be replaced by LogLevel
as a property name.POWERTOOLS_LOG_LEVEL
and when setting it in code using [Logging(LogLevel = )]
Here's a sequence diagram to demonstrate how ALC will drop both Information
and Debug
logs emitted from Logger
, when ALC log level is stricter than Logger
.
sequenceDiagram
title Lambda ALC allows WARN logs only
participant Lambda service
participant Lambda function
participant Application Logger
Note over Lambda service: AWS_LAMBDA_LOG_LEVEL="WARN"
Note over Application Logger: POWERTOOLS_LOG_LEVEL="DEBUG"
Lambda service->>Lambda function: Invoke (event)
Lambda function->>Lambda function: Calls handler
Lambda function->>Application Logger: Logger.Warning("Something happened")
Lambda function-->>Application Logger: Logger.Debug("Something happened")
Lambda function-->>Application Logger: Logger.Information("Something happened")
Lambda service->>Lambda service: DROP INFO and DEBUG logs
Lambda service->>CloudWatch Logs: Ingest error logs
Priority of log level settings in Powertools for AWS Lambda
We prioritise log level settings in this order:
[Logging(LogLevel = )]
If you set Logger
level lower than ALC, we will emit a warning informing you that your messages will be discarded by Lambda.
Standard structured keys¶NOTE With ALC enabled, we are unable to increase the minimum log level below the
AWS_LAMBDA_LOG_LEVEL
environment variable value, see AWS Lambda service documentation for more details.
Your logs will always include the following keys to your structured logging:
Key Type Example Description Timestamp string "2020-05-24 18:17:33,774" Timestamp of actual log statement Level string "Information" Logging level Name string "Powertools for AWS Lambda (.NET) Logger" Logger name ColdStart bool true ColdStart value. Service string "payment" Service name defined. "service_undefined" will be used if unknown SamplingRate int 0.1 Debug logging sampling rate in percentage e.g. 10% in this case Message string "Collecting payment" Log statement value. Unserializable JSON values will be cast to string FunctionName string "example-powertools-HelloWorldFunction-1P1Z6B39FLU73" FunctionVersion string "12" FunctionMemorySize string "128" FunctionArn string "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73" XRayTraceId string "1-5759e988-bd862e3fe1be46a994272793" X-Ray Trace ID when Lambda function has enabled Tracing FunctionRequestId string "899856cb-83d1-40d7-8611-9e78f15f32f4" AWS Request ID from lambda context Logging incoming event¶When debugging in non-production environments, you can instruct Logger to log the incoming event with LogEvent
parameter or via POWERTOOLS_LOGGER_LOG_EVENT
environment variable.
Warning
Log event is disabled by default to prevent sensitive info being logged.
Function.cs
1 2 3 4 5 6 7 8 9 10 11 12
/**
* Handler for requests to Lambda function.
*/
public class Function
{
[Logging(LogEvent = true)]
public async Task<APIGatewayProxyResponse> FunctionHandler
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
...
}
}
Setting a Correlation ID¶
You can set a Correlation ID using CorrelationIdPath
parameter by passing a JSON Pointer expression.
Attention
The JSON Pointer expression is case sensitive
. In the bellow example /headers/my_request_id_header
would work but /Headers/my_request_id_header
would not find the element.
Function.csExample EventExample CloudWatch Logs excerpt
1 2 3 4 5 6 7 8 9 10 11 12
/**
* Handler for requests to Lambda function.
*/
public class Function
{
[Logging(CorrelationIdPath = "/headers/my_request_id_header")]
public async Task<APIGatewayProxyResponse> FunctionHandler
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
...
}
}
{
"headers": {
"my_request_id_header": "correlation_id_value"
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
{
"cold_start": true,
"xray_trace_id": "1-61b7add4-66532bb81441e1b060389429",
"function_name": "test",
"function_version": "$LATEST",
"function_memory_size": 128,
"function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"level": "Information",
"service": "lambda-example",
"name": "AWS.Lambda.Powertools.Logging.Logger",
"message": "Collecting payment",
"sampling_rate": 0.7,
"correlation_id": "correlation_id_value",
}
We provide built-in JSON Pointer expression for known event sources, where either a request ID or X-Ray Trace ID are present.
Function.csExample EventExample CloudWatch Logs excerpt
1 2 3 4 5 6 7 8 9 10 11 12
/**
* Handler for requests to Lambda function.
*/
public class Function
{
[Logging(CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)]
public async Task<APIGatewayProxyResponse> FunctionHandler
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
...
}
}
{
"RequestContext": {
"RequestId": "correlation_id_value"
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
{
"cold_start": true,
"xray_trace_id": "1-61b7add4-66532bb81441e1b060389429",
"function_name": "test",
"function_version": "$LATEST",
"function_memory_size": 128,
"function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"level": "Information",
"service": "lambda-example",
"name": "AWS.Lambda.Powertools.Logging.Logger",
"message": "Collecting payment",
"sampling_rate": 0.7,
"correlation_id": "correlation_id_value",
}
Appending additional keys¶
Custom keys are persisted across warm invocations
Always set additional keys as part of your handler to ensure they have the latest value, or explicitly clear them with [`ClearState=true`](#clearing-all-state).
You can append your own keys to your existing logs via AppendKey
. Typically this value would be passed into the function via the event. Appended keys are added to all subsequent log entries in the current execution from the point the logger method is called. To ensure the key is added to all log entries, call this method as early as possible in the Lambda handler.
Function.csExample CloudWatch Logs excerpt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/**
* Handler for requests to Lambda function.
*/
public class Function
{
[Logging(LogEvent = true)]
public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest apigwProxyEvent,
ILambdaContext context)
{
var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId;
var lookupInfo = new Dictionary<string, object>()
{
{"LookupInfo", new Dictionary<string, object>{{ "LookupId", requestContextRequestId }}}
};
// Appended keys are added to all subsequent log entries in the current execution.
// Call this method as early as possible in the Lambda handler.
// Typically this is value would be passed into the function via the event.
// Set the ClearState = true to force the removal of keys across invocations,
Logger.AppendKeys(lookupInfo);
Logger.LogInformation("Getting ip address from external service");
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
{
"cold_start": false,
"xray_trace_id": "1-622eede0-647960c56a91f3b071a9fff1",
"lookup_info": {
"lookup_id": "4c50eace-8b1e-43d3-92ba-0efacf5d1625"
},
"function_name": "PowertoolsLoggingSample-HelloWorldFunction-hm1r10VT3lCy",
"function_version": "$LATEST",
"function_memory_size": 256,
"function_arn": "arn:aws:lambda:ap-southeast-2:538510314095:function:PowertoolsLoggingSample-HelloWorldFunction-hm1r10VT3lCy",
"function_request_id": "96570b2c-f00e-471c-94ad-b25e95ba7347",
"timestamp": "2022-03-14T07:25:20.9418065Z",
"level": "Information",
"service": "powertools-dotnet-logging-sample",
"name": "AWS.Lambda.Powertools.Logging.Logger",
"message": "Getting ip address from external service"
}
Removing additional keys¶
You can remove any additional key from entry using Logger.RemoveKeys()
.
Function.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/**
* Handler for requests to Lambda function.
*/
public class Function
{
[Logging(LogEvent = true)]
public async Task<APIGatewayProxyResponse> FunctionHandler
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
...
Logger.AppendKey("test", "willBeLogged");
...
var customKeys = new Dictionary<string, string>
{
{"test1", "value1"},
{"test2", "value2"}
};
Logger.AppendKeys(customKeys);
...
Logger.RemoveKeys("test");
Logger.RemoveKeys("test1", "test2");
...
}
}
Extra keys allow you to append additional keys to a log entry. Unlike AppendKey
, extra keys will only apply to the current log entry.
Extra keys argument is available for all log levels' methods, as implemented in the standard logging library - e.g. Logger.Information, Logger.Warning.
It accepts any dictionary, and all keyword arguments will be added as part of the root structure of the logs for that log statement.
Info
Any keyword argument added using extra keys will not be persisted for subsequent messages.
Function.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/**
* Handler for requests to Lambda function.
*/
public class Function
{
[Logging(LogEvent = true)]
public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest apigwProxyEvent,
ILambdaContext context)
{
var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId;
var lookupId = new Dictionary<string, object>()
{
{ "LookupId", requestContextRequestId }
};
// Appended keys are added to all subsequent log entries in the current execution.
// Call this method as early as possible in the Lambda handler.
// Typically this is value would be passed into the function via the event.
// Set the ClearState = true to force the removal of keys across invocations,
Logger.AppendKeys(lookupId);
}
Clearing all state¶
Logger is commonly initialized in the global scope. Due to Lambda Execution Context reuse, this means that custom keys can be persisted across invocations. If you want all custom keys to be deleted, you can use ClearState=true
attribute on [Logging]
attribute.
Function.cs#1 Request#2 Request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/**
* Handler for requests to Lambda function.
*/
public class Function
{
[Logging(ClearState = true)]
public async Task<APIGatewayProxyResponse> FunctionHandler
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
...
if (apigProxyEvent.Headers.ContainsKey("SomeSpecialHeader"))
{
Logger.AppendKey("SpecialKey", "value");
}
Logger.LogInformation("Collecting payment");
...
}
}
1 2 3 4 5 6 7 8 9 10 11 12
{
"level": "Information",
"message": "Collecting payment",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"service": "payment",
"cold_start": true,
"function_name": "test",
"function_memory_size": 128,
"function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"special_key": "value"
}
{
"level": "Information",
"message": "Collecting payment",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"service": "payment",
"cold_start": true,
"function_name": "test",
"function_memory_size": 128,
"function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72"
}
Sampling debug logs¶
You can dynamically set a percentage of your logs to DEBUG level via env var POWERTOOLS_LOGGER_SAMPLE_RATE
or via SamplingRate
parameter on attribute.
Info
Configuration on environment variable is given precedence over sampling rate configuration on attribute, provided it's in valid value range.
Sampling via attribute parameterSampling via environment variable
1 2 3 4 5 6 7 8 9 10 11 12
/**
* Handler for requests to Lambda function.
*/
public class Function
{
[Logging(SamplingRate = 0.5)]
public async Task<APIGatewayProxyResponse> FunctionHandler
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
...
}
}
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
...
Environment:
Variables:
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.5
Configure Log Output Casing¶
By definition Powertools for AWS Lambda (.NET) outputs logging keys using snake case (e.g. "function_memory_size": 128). This allows developers using different Powertools for AWS Lambda (.NET) runtimes, to search logs across services written in languages such as Python or TypeScript.
If you want to override the default behavior you can either set the desired casing through attributes, as described in the example below, or by setting the POWERTOOLS_LOGGER_CASE
environment variable on your AWS Lambda function. Allowed values are: CamelCase
, PascalCase
and SnakeCase
.
Output casing via attribute parameter
1 2 3 4 5 6 7 8 9 10 11 12
/**
* Handler for requests to Lambda function.
*/
public class Function
{
[Logging(LoggerOutputCase = LoggerOutputCase.CamelCase)]
public async Task<APIGatewayProxyResponse> FunctionHandler
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
...
}
}
Below are some output examples for different casing.
Camel CasePascal CaseSnake Case
{
"level": "Information",
"message": "Collecting payment",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"service": "payment",
"coldStart": true,
"functionName": "test",
"functionMemorySize": 128,
"functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"functionRequestId": "52fdfc07-2182-154f-163f-5f0f9a621d72"
}
{
"Level": "Information",
"Message": "Collecting payment",
"Timestamp": "2021-12-13T20:32:22.5774262Z",
"Service": "payment",
"ColdStart": true,
"FunctionName": "test",
"FunctionMemorySize": 128,
"FunctionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"FunctionRequestId": "52fdfc07-2182-154f-163f-5f0f9a621d72"
}
{
"level": "Information",
"message": "Collecting payment",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"service": "payment",
"cold_start": true,
"function_name": "test",
"function_memory_size": 128,
"function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72"
}
Custom Log formatter (Bring Your Own Formatter)¶
You can customize the structure (keys and values) of your log entries by implementing a custom log formatter and override default log formatter using Logger.UseFormatter
method. You can implement a custom log formatter by inheriting the ILogFormatter
class and implementing the object FormatLogEntry(LogEntry logEntry)
method.
Function.csCustomLogFormatter.csExample CloudWatch Logs excerpt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/**
* Handler for requests to Lambda function.
*/
public class Function
{
/// <summary>
/// Function constructor
/// </summary>
public Function()
{
Logger.UseFormatter(new CustomLogFormatter());
}
[Logging(CorrelationIdPath = "/headers/my_request_id_header", SamplingRate = 0.7)]
public async Task<APIGatewayProxyResponse> FunctionHandler
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
...
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
public class CustomLogFormatter : ILogFormatter
{
public object FormatLogEntry(LogEntry logEntry)
{
return new
{
Message = logEntry.Message,
Service = logEntry.Service,
CorrelationIds = new
{
AwsRequestId = logEntry.LambdaContext?.AwsRequestId,
XRayTraceId = logEntry.XRayTraceId,
CorrelationId = logEntry.CorrelationId
},
LambdaFunction = new
{
Name = logEntry.LambdaContext?.FunctionName,
Arn = logEntry.LambdaContext?.InvokedFunctionArn,
MemoryLimitInMB = logEntry.LambdaContext?.MemoryLimitInMB,
Version = logEntry.LambdaContext?.FunctionVersion,
ColdStart = logEntry.ColdStart,
},
Level = logEntry.Level.ToString(),
Timestamp = logEntry.Timestamp.ToString("o"),
Logger = new
{
Name = logEntry.Name,
SampleRate = logEntry.SamplingRate
},
};
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
{
"Message": "Test Message",
"Service": "lambda-example",
"CorrelationIds": {
"AwsRequestId": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"XRayTraceId": "1-61b7add4-66532bb81441e1b060389429",
"CorrelationId": "correlation_id_value"
},
"LambdaFunction": {
"Name": "test",
"Arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"MemorySize": 128,
"Version": "$LATEST",
"ColdStart": true
},
"Level": "Information",
"Timestamp": "2021-12-13T20:32:22.5774262Z",
"Logger": {
"Name": "AWS.Lambda.Powertools.Logging.Logger",
"SampleRate": 0.7
}
}
AOT Support¶
Info
If you want to use the LogEvent
, Custom Log Formatter
features, or serialize your own types when Logging events, you need to make changes in your Lambda Main
method.
Info
Starting from version 1.6.0, it is required to update the Amazon.Lambda.Serialization.SystemTextJson NuGet package to version 2.4.3 in your csproj.
Configure¶Replace SourceGeneratorLambdaJsonSerializer
with PowertoolsSourceGeneratorSerializer
.
This change enables Powertools to construct an instance of JsonSerializerOptions
used to customize the serialization and deserialization of Lambda JSON events and your own types.
BeforeAfter
Func<APIGatewayHttpApiV2ProxyRequest, ILambdaContext, Task<APIGatewayHttpApiV2ProxyResponse>> handler = FunctionHandler;
await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer<MyCustomJsonSerializerContext>())
.Build()
.RunAsync();
Func<APIGatewayHttpApiV2ProxyRequest, ILambdaContext, Task<APIGatewayHttpApiV2ProxyResponse>> handler = FunctionHandler;
await LambdaBootstrapBuilder.Create(handler, new PowertoolsSourceGeneratorSerializer<MyCustomJsonSerializerContext>())
.Build()
.RunAsync();
For example when you have your own Demo type
public class Demo
{
public string Name { get; set; }
public Headers Headers { get; set; }
}
To be able to serialize it in AOT you have to have your own JsonSerializerContext
[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))]
[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))]
[JsonSerializable(typeof(Demo))]
public partial class MyCustomJsonSerializerContext : JsonSerializerContext
{
}
When you update your code to use PowertoolsSourceGeneratorSerializer<MyCustomJsonSerializerContext>
, we combine your JsonSerializerContext
with Powertools' JsonSerializerContext
. This allows Powertools to serialize your types and Lambda events.
To use a custom log formatter with AOT, pass an instance of ILogFormatter
to PowertoolsSourceGeneratorSerializer
instead of using the static Logger.UseFormatter
in the Function constructor as you do in non-AOT Lambdas.
Function Main methodCustomLogFormatter.cs
Func<APIGatewayHttpApiV2ProxyRequest, ILambdaContext, Task<APIGatewayHttpApiV2ProxyResponse>> handler = FunctionHandler;
await LambdaBootstrapBuilder.Create(handler,
new PowertoolsSourceGeneratorSerializer<LambdaFunctionJsonSerializerContext>
(
new CustomLogFormatter()
)
)
.Build()
.RunAsync();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
public class CustomLogFormatter : ILogFormatter
{
public object FormatLogEntry(LogEntry logEntry)
{
return new
{
Message = logEntry.Message,
Service = logEntry.Service,
CorrelationIds = new
{
AwsRequestId = logEntry.LambdaContext?.AwsRequestId,
XRayTraceId = logEntry.XRayTraceId,
CorrelationId = logEntry.CorrelationId
},
LambdaFunction = new
{
Name = logEntry.LambdaContext?.FunctionName,
Arn = logEntry.LambdaContext?.InvokedFunctionArn,
MemoryLimitInMB = logEntry.LambdaContext?.MemoryLimitInMB,
Version = logEntry.LambdaContext?.FunctionVersion,
ColdStart = logEntry.ColdStart,
},
Level = logEntry.Level.ToString(),
Timestamp = logEntry.Timestamp.ToString("o"),
Logger = new
{
Name = logEntry.Name,
SampleRate = logEntry.SamplingRate
},
};
}
}
Anonymous types¶
Note
While we support anonymous type serialization by converting to a Dictionary<string, object>
, this is not a best practice and is not recommended when using native AOT.
We recommend using concrete classes and adding them to your JsonSerializerContext
.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4