Skip to content

add SqlClientListener for diagnostic logging #914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/label-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,42 @@ Needs Logs:

3. Get the log file and attach it to this issue. By default this will be in `%TMP%/LogFiles/Application/Functions/Host`.

**NOTE** Debug logging will include information such as Database and table names, if you do not wish to include this information you can either redact it from the logs before attaching or let us know and we will provide a way to send logs directly to us.

#actions for Needs Connection Logs
Needs Connection Logs:
comment: |
We need more info to debug your particular issue. If you could attach your SQL Client logs to the issue, it would help us fix the issue much faster.

1. Set the AzureFunctions_SqlBindings_VerboseLogging to `true` in your `*.settings.json` file.

```json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "<dotnet>",
"SqlConnectionString": "<Your Connection string>",
"AzureFunctions_SqlBindings_VerboseLogging": "true",
}
}
```

2. Set the default logging level to `Debug` and enable logging to a file. To do this ensure the following entries are in your `host.json`. ([instructions](https://learn.microsoft.com/azure/azure-functions/configure-monitoring?tabs=v2#configure-log-levels))

```json
{
"logging": {
"fileLoggingMode": "always",
"logLevel": {
"default": "Debug"
}
}
}
```

3. Restart your function and reproduce your issue

4. Get the log file and attach it to this issue. By default this will be in `%TMP%/LogFiles/Application/Functions/Host`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, looking at this it would be good to include instructions (for both this and the other logging message) for doing this in a deployed function too since they won't be the same. Not sure offhand what the easiest way to export the logs would be, so that might take some investigation.

In the meantime I think we can actually revert the change I had you add earlier about the Function App settings and instead specify that these instructions are for local development only. Then when we figure out a way for deployed functions we can come back and update both with those instructions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the note! Might be worth making an actual issue to track updating this with instructions for deployed functions as well.

(although if we do that it might actually be best to just update the instructions in our docs and then link to those from here so we don't have to remember to keep both in sync)


**NOTE** Debug logging will include information such as Database and table names, if you do not wish to include this information you can either redact it from the logs before attaching or let us know and we will provide a way to send logs directly to us.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [Known/By Design Issues](#knownby-design-issues)
- [Output Bindings](#output-bindings)
- [Telemetry](#telemetry)
- [Troubleshooting](#troubleshooting)
- [Privacy Statement](#privacy-statement)
- [Trademarks](#trademarks)

Expand Down Expand Up @@ -71,6 +72,10 @@ Below is a list of common issues that users may run into when using the SQL Bind

This extension collects usage data in order to help us improve your experience. The data is anonymous and doesn't include any personal information. You can opt-out of telemetry by setting the `AZUREFUNCTIONS_SQLBINDINGS_TELEMETRY_OPTOUT` environment variable or the `AzureFunctionsSqlBindingsTelemetryOptOut` app setting (in your `*.settings.json` file) to '1', 'true' or 'yes';

## Troubleshooting

For troubleshooting SQL Client issues, You can enable verbose logging by setting the `AzureFunctions_SqlBindings_VerboseLogging` app setting (in your `*.settings.json` file) to '1', 'true' or 'yes';

## Privacy Statement

To learn more about our Privacy Statement visit [this link](https://go.microsoft.com/fwlink/?LinkID=824704).
Expand Down
26 changes: 25 additions & 1 deletion src/SqlBindingConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ namespace Microsoft.Azure.WebJobs.Extensions.Sql
/// Exposes SQL input and output bindings
/// </summary>
[Extension("sql")]
internal class SqlBindingConfigProvider : IExtensionConfigProvider
internal class SqlBindingConfigProvider : IExtensionConfigProvider, IDisposable
{
private readonly IConfiguration _configuration;
private readonly ILoggerFactory _loggerFactory;
private SqlClientListener sqlClientListener;
public const string VerboseLoggingSettingName = "AzureFunctions_SqlBindings_VerboseLogging";

/// <summary>
/// Initializes a new instance of the <see cref="SqlBindingConfigProvider"/> class.
Expand Down Expand Up @@ -54,6 +56,12 @@ public void Initialize(ExtensionConfigContext context)
}
ILogger logger = this._loggerFactory.CreateLogger(LogCategories.Bindings);
TelemetryInstance.Initialize(this._configuration, logger);
// Only enable SQL Client logging when VerboseLogging is set in the config to avoid extra overhead when the
// detailed logging it provides isn't needed
if (this.sqlClientListener == null && Utils.GetConfigSettingAsBool(VerboseLoggingSettingName, this._configuration))
{
this.sqlClientListener = new SqlClientListener(logger);
}
LogDependentAssemblyVersions(logger);
#pragma warning disable CS0618 // Fine to use this for our stuff
FluentBindingRule<SqlAttribute> inputOutputRule = context.AddBindingRule<SqlAttribute>();
Expand Down Expand Up @@ -90,6 +98,22 @@ private static void LogDependentAssemblyVersions(ILogger logger)

}
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Dispose managed resources.
this.sqlClientListener?.Dispose();
}
// Free native resources.
}

public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

}

/// <summary>
Expand Down
71 changes: 71 additions & 0 deletions src/SqlClientEventListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Linq;
using System.Diagnostics.Tracing;
using System;
using Microsoft.Extensions.Logging;

namespace Microsoft.Azure.WebJobs.Extensions.Sql
{

/// <summary>
/// This listener class will listen for events from the SqlClientEventSource class
/// and forward them to the logger.
/// </summary>
public class SqlClientListener : EventListener
{
private readonly ILogger _logger;

public SqlClientListener(ILogger logger)
{
this._logger = logger;
}

protected override void OnEventSourceCreated(EventSource eventSource)
{
// Only enable events from SqlClientEventSource.
if (string.CompareOrdinal(eventSource.Name, "Microsoft.Data.SqlClient.EventSource") == 0)
{
// Use EventKeyWord 2 to capture basic application flow events.
// See https://docs.microsoft.com/sql/connect/ado-net/enable-eventsource-tracing for all available keywords.
this.EnableEvents(eventSource, EventLevel.Informational, (EventKeywords)2);
}
}

/// <summary>
/// This callback runs whenever an event is written by SqlClientEventSource.
/// Event data is accessed through the EventWrittenEventArgs parameter.
/// </summary>
/// <param name="eventData">The data for the event</param>
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
try
{
if (eventData.Payload == null)
{
return;
}
object[] values = new object[eventData.Payload.Count];
string[] keys = new string[eventData.PayloadNames.Count];

eventData.PayloadNames.CopyTo(keys, 0);
eventData.Payload.CopyTo(values, 0);
var payloadDictionary = keys.Select((k, i) => new { k, v = values[i].ToString() })
.ToDictionary(x => x.k, x => x.v);
foreach (object payload in eventData.Payload)
{
if (payload != null)
{
this._logger.LogTrace($"Sending event {eventData.EventName}. Properties: {Utils.JsonSerializeObject(payloadDictionary)}");
}
}
}
catch (Exception ex)
{
this._logger.LogError($"Error sending event {eventData.EventName}. Message={ex.Message}");

}
}
}
}