I am trying to enable sampling with my AppInsightsHelper class which starts Depedancy operations to track performance.
This is how I am initializing my TelematryClient:
public ApplicationInsightsHelper(string key)
{
var config = TelemetryConfiguration.CreateDefault();
config.InstrumentationKey = key;
config.DefaultTelemetrySink.TelemetryProcessorChainBuilder.UseAdaptiveSampling(maxTelemetryItemsPerSecond: 1);
_telemetryClient = new TelemetryClient(config);
}
and then Starting and Stopping the operation:
IOperationHolder<DependencyTelemetry> operation = null;
operation = _telemetryClient.StartOperation<DependencyTelemetry>(friendlyName);
operation.Telemetry.Name = friendlyName;
operation.Telemetry.Type = type;
operation.Telemetry.Timestamp = DateTime.UtcNow;
operation.Telemetry.Duration = DateTime.UtcNow - operation.Telemetry.Timestamp;
_telemetryClient.StopOperation(operation);
The issue is that the above code seems to ignore the Sampling setting and all operations are traced. I have also included : excludedTypes: "Dependency" within the UseAdaptiveSampling to see if anything happens and as expected the Dependencies are not ignored.
If it's an azure function, you can set sampling via host.json, see here and here for details. An example as below:
{
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"maxTelemetryItemsPerSecond" : 1
}
}
}
}
And if you want to use TelemetryClient with the settings, you should follow this article. In the constructor of the azure function, use code like below:
/// Using dependency injection will guarantee that you use the same configuration for telemetry collected automatically and manually.
public HttpTrigger2(TelemetryConfiguration telemetryConfiguration)
{
this.telemetryClient = new TelemetryClient(telemetryConfiguration);
}
But as of now, there is an issue by using telemetryConfiguration.
This has been worked for me for ASP.NET web application. I have added below configuration and specifically added my 'MaksingTelemetryInitializer'.
public void StartApplicationInsights(string logType)
{
string appInsightsComponentId = string.Empty;
try
{
telemetryClient = new TelemetryClient();
TelemetryConfiguration.Active.InstrumentationKey = GetConfigvalue("AppInsightsAppId"); ;
TelemetryConfiguration.Active.TelemetryInitializers.Add(new MaskingTelemetryInitializer());
}
catch (Exception exception)
{
// Log Exception to WadLog if logging to Wadlog is enabled
if (logType != LoggingType.Both) return;
WadLogWriter.LogToWadLogs(Logger.BuildErrorString(exception), EventLevel.Error);
}
}
Here I wanted mask PII data email id, it is working.
Related
I'm using OmniSharp's C# LSP server to implement a simple parsing/language service for a VS Code plugin. I've managed to get the basics up and running, but I've not been able to figure out how to push diagnostic messages to VS Code (like in this typescript sample).
Does anyone have any sample code/hints that would be of use?
Thanks!
Having spoken with #david-driscoll, it turns out I needed to stash a reference to ILanguageServerFacade in my constructor and use the PublishDiagnostics extension method on TextDocument. Ie:
public class TextDocumentSyncHandler : ITextDocumentSyncHandler
{
private readonly ILanguageServerFacade _facade;
public TextDocumentSyncHandler(ILanguageServerFacade facade)
{
_facade = facade;
}
public Task<Unit> Handle(DidChangeTextDocumentParams request, CancellationToken cancellationToken)
{
// Parse your stuff here
// Diagnostics are sent a document at a time, this example is for demonstration purposes only
var diagnostics = ImmutableArray<Diagnostic>.Empty.ToBuilder();
diagnostics.Add(new Diagnostic()
{
Code = "ErrorCode_001",
Severity = DiagnosticSeverity.Error,
Message = "Something bad happened",
Range = new Range(0, 0, 0, 0),
Source = "XXX",
Tags = new Container<DiagnosticTag>(new DiagnosticTag[] { DiagnosticTag.Unnecessary })
});
_facade.TextDocument.PublishDiagnostics(new PublishDiagnosticsParams()
{
Diagnostics = new Container<Diagnostic>(diagnostics.ToArray()),
Uri = request.TextDocument.Uri,
Version = request.TextDocument.Version
});
return Unit.Task;
}
}
For real code, you would want a centralised array of Diagnostic objects, but this shows the basics of how to get it done.
Thank you David!
I implemented App.Metrics into my wcf application (App.Metrics ver 3.1.0).
When I check url in which data is uploaded I found that app isn't filled:
Tried to figured out reason of this behavior I found manual:
https://www.app-metrics.io/getting-started/fundamentals/tagging-organizing/
It said that AssemblyName needs to be filled, but I double-checked it - csproj file contain next row:
<AssemblyName>MyWebService</AssemblyName>
How can I fill this app property in metrics?
startup.cs:
var metrics = MetricsProvider.Instance.Metrics;
SetMetricsAppTag(metrics, Assembly.GetExecutingAssembly().GetName().Name);
private static void SetMetricsAppTag(IMetricsRoot metricsRoot, string appTagValue)
{
if (!metricsRoot.Options.GlobalTags.ContainsKey("app"))
{
metricsRoot.Options.GlobalTags.Add("app", appTagValue);
}
else if (string.IsNullOrEmpty(metricsRoot.Options.GlobalTags["app"]) || metricsRoot.Options.GlobalTags["app"] == "unknown")
{
metricsRoot.Options.GlobalTags["app"] = appTagValue;
}
}
a better way will be to do in startup-
var metrics = AppMetrics.CreateDefaultBuilder().
Configuration.
Configure(options => options.AddAppTag(appName: "nexus"))
.Build();
services.AddMetrics(metrics);
services.AddMetricsTrackingMiddleware();
services.AddMetricsEndpoints(opt =>
{
opt.MetricsTextEndpointOutputFormatter = new MetricsPrometheusTextOutputFormatter();
opt.MetricsEndpointOutputFormatter = new MetricsPrometheusProtobufOutputFormatter();
opt.EnvironmentInfoEndpointEnabled = false;
});
Hello i am having a hard time configuring my cosmos db to botframework.Before when using memory storage it is working fine. I am reading this and this as a guide. I included the errors with comments within the codes. Can anyone help me with this. I would greatly appreciate the help. I have been researching this for 3 days already. Thank you!
public class Startup
{
private const string CosmosServiceEndpoint = "xxxxxxxxxxx";
private const string CosmosDBKey = "xxxxxxxxxxx";
private const string CosmosDBDatabaseName = "xxxxxxxxxxx";
private const string CosmosDBCollectionNameConState = "conversationState";
private const string CosmosDBCollectionNameUserState = "userState";
private ILoggerFactory _loggerFactory;
private bool _isProduction = false;
public Startup(IHostingEnvironment env)
{
_isProduction = env.IsProduction();
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddBot<BasicBot>(options =>
{
ILogger logger = _loggerFactory.CreateLogger<BasicBot>();
var secretKey = Configuration.GetSection("botFileSecret")?.Value;
var botFilePath = Configuration.GetSection("botFilePath")?.Value;
if (!File.Exists(botFilePath))
{
throw new FileNotFoundException($"The .bot configuration file was not found. botFilePath: {botFilePath}");
}
BotConfiguration botConfig = null;
try
{
botConfig = BotConfiguration.Load(botFilePath ?? #".\echo-with-counter.bot", secretKey);
}
catch
{
var msg = #"Error reading bot file. Please ensure you have valid botFilePath and botFileSecret set for your environment.
- You can find the botFilePath and botFileSecret in the Azure App Service application settings.
- If you are running this bot locally, consider adding a appsettings.json file with botFilePath and botFileSecret.
- See https://aka.ms/about-bot-file to learn more about .bot file its use and bot configuration.
";
logger.LogError(msg);
throw new InvalidOperationException(msg);
}
services.AddSingleton(sp => botConfig);
var environment = _isProduction ? "production" : "development";
var service = botConfig.Services.FirstOrDefault(s => s.Type == "endpoint" && s.Name == environment);
if (service == null && _isProduction)
{
service = botConfig.Services.Where(s => s.Type == "endpoint" && s.Name == "development").FirstOrDefault();
logger.LogWarning("Attempting to load development endpoint in production environment.");
}
if (!(service is EndpointService endpointService))
{
throw new InvalidOperationException($"The .bot file does not contain an endpoint with name '{environment}'.");
}
options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);
options.OnTurnError = async (context, exception) =>
{
logger.LogError($"Exception caught : {exception}");
await context.SendActivityAsync("Sorry, it looks like something went wrong.");
};
// The Memory Storage used here is for local bot debugging only. When the bot
// is restarted, everything stored in memory will be gone.
// IStorage dataStore = new MemoryStorage();
// error : COSMOSDBSTORAGE DOES NOT CONTAIN CONSTRUCTOR TAKES 4 ARGUMENTS
//IStorage dataStoreConversationState =
// new CosmosDbStorage(
// uri,
// "** auth key **",
// "helloworldbot",
// "conversationstate");
var uri = new Uri(CosmosServiceEndpoint);
IStorage dataStoreConversationState =
new CosmosDbStorage(new CosmosDbStorageOptions
{
AuthKey = CosmosDBKey,
CollectionId = CosmosDBCollectionNameConState,
CosmosDBEndpoint = new Uri(CosmosServiceEndpoint),
DatabaseId = CosmosDBDatabaseName,
});
IStorage dataStoreUserState =
new CosmosDbStorage(new CosmosDbStorageOptions
{
AuthKey = CosmosDBKey,
CollectionId = CosmosDBCollectionNameUserState,
CosmosDBEndpoint = new Uri(CosmosServiceEndpoint),
DatabaseId = CosmosDBDatabaseName,
});
//error : THE NON GENERIC TYPE "CONVERSATIONsTATE" CANNOT BE USED WITH TYPED ARGUMENTS
options.Middleware.Add(new ConversationState<BasicState>(dataStoreConversationState));
options.Middleware.Add(new UserState<BasicUserState>(dataStoreUserState));
}
There's a good chance that the reason this isn't working for you is because both of those links mention you need to create a New Collection in your CosmosDB resource in Azure. Microsoft recently updated the CosmosDB resource to require that new collections are made with Partition Keys, which aren't yet supported in Bot Framework. There's currently a Design Change Request to add this ability, but it's being stalled by the C# Cosmos SDK.
In the meantime, start by making the Cosmos resource in Azure and DO NOT make a database or collection. ONLY make the Cosmos resource. The bot framework SDK is set up to make a new DB and collection if the one you specify doesn't exist, and it can make one without partitions...so let the bot do the work here.
I used the second link you posted to change the Simple Prompt bot sample to work with Cosmos. Note: The endpoint and key are the default ones for the CosmosDB Emulator, which you can use to test locally, if you prefer.
Here is my startup.cs:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Azure;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Integration;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Configuration;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.BotBuilderSamples
{
/// <summary>
/// The Startup class configures services and the app's request pipeline.
/// </summary>
public class Startup
{
private const string CosmosServiceEndpoint = "https://localhost:8081";
private const string CosmosDBKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
private const string CosmosDBDatabaseName = "bot-cosmos-sql-db";
private const string CosmosDBCollectionName = "bot-storage";
private static readonly CosmosDbStorage _myStorage = new CosmosDbStorage(new CosmosDbStorageOptions
{
AuthKey = CosmosDBKey,
CollectionId = CosmosDBCollectionName,
CosmosDBEndpoint = new Uri(CosmosServiceEndpoint),
DatabaseId = CosmosDBDatabaseName,
});
private ILoggerFactory _loggerFactory;
private bool _isProduction = false;
public Startup(IHostingEnvironment env)
{
_isProduction = env.IsProduction();
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
/// <summary>
/// Gets the configuration that represents a set of key/value application configuration properties.
/// </summary>
/// <value>
/// The <see cref="IConfiguration"/> that represents a set of key/value application configuration properties.
/// </value>
public IConfiguration Configuration { get; }
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> specifies the contract for a collection of service descriptors.</param>
/// <seealso cref="IStatePropertyAccessor{T}"/>
/// <seealso cref="https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection"/>
/// <seealso cref="https://learn.microsoft.com/en-us/azure/bot-service/bot-service-manage-channels?view=azure-bot-service-4.0"/>
public void ConfigureServices(IServiceCollection services)
{
services.AddBot<SimplePromptBot>(options =>
{
var secretKey = Configuration.GetSection("botFileSecret")?.Value;
var botFilePath = Configuration.GetSection("botFilePath")?.Value;
if (!File.Exists(botFilePath))
{
throw new FileNotFoundException($"The .bot configuration file was not found. botFilePath: {botFilePath}");
}
// Loads .bot configuration file and adds a singleton that your Bot can access through dependency injection.
var botConfig = BotConfiguration.Load(botFilePath ?? #".\simple-prompt.bot", secretKey);
services.AddSingleton(sp => botConfig ?? throw new InvalidOperationException($"The .bot configuration file could not be loaded. botFilePath: {botFilePath}"));
// Retrieve current endpoint.
var environment = _isProduction ? "production" : "development";
var service = botConfig.Services.FirstOrDefault(s => s.Type == "endpoint" && s.Name == environment);
if (!(service is EndpointService endpointService))
{
throw new InvalidOperationException($"The .bot file does not contain an endpoint with name '{environment}'.");
}
options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);
// Creates a logger for the application to use.
ILogger logger = _loggerFactory.CreateLogger<SimplePromptBot>();
// Catches any errors that occur during a conversation turn and logs them.
options.OnTurnError = async (context, exception) =>
{
logger.LogError($"Exception caught : {exception}");
await context.SendActivityAsync("Sorry, it looks like something went wrong.");
};
// Memory Storage is for local bot debugging only. When the bot
// is restarted, everything stored in memory will be gone.
//IStorage dataStore = new MemoryStorage();
// For production bots use the Azure Blob or
// Azure CosmosDB storage providers. For the Azure
// based storage providers, add the Microsoft.Bot.Builder.Azure
// Nuget package to your solution. That package is found at:
// https://www.nuget.org/packages/Microsoft.Bot.Builder.Azure/
// Uncomment the following lines to use Azure Blob Storage
// //Storage configuration name or ID from the .bot file.
// const string StorageConfigurationId = "<STORAGE-NAME-OR-ID-FROM-BOT-FILE>";
// var blobConfig = botConfig.FindServiceByNameOrId(StorageConfigurationId);
// if (!(blobConfig is BlobStorageService blobStorageConfig))
// {
// throw new InvalidOperationException($"The .bot file does not contain an blob storage with name '{StorageConfigurationId}'.");
// }
// // Default container name.
// const string DefaultBotContainer = "<DEFAULT-CONTAINER>";
// var storageContainer = string.IsNullOrWhiteSpace(blobStorageConfig.Container) ? DefaultBotContainer : blobStorageConfig.Container;
// IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureBlobStorage(blobStorageConfig.ConnectionString, storageContainer);
// Create Conversation State object.
// The Conversation State object is where we persist anything at the conversation-scope.
var conversationState = new ConversationState(_myStorage);
options.State.Add(conversationState);
});
services.AddSingleton(sp =>
{
// We need to grab the conversationState we added on the options in the previous step.
var options = sp.GetRequiredService<IOptions<BotFrameworkOptions>>().Value;
if (options == null)
{
throw new InvalidOperationException("BotFrameworkOptions must be configured prior to setting up the State Accessors");
}
var conversationState = options.State.OfType<ConversationState>().FirstOrDefault();
if (conversationState == null)
{
throw new InvalidOperationException("ConversationState must be defined and added before adding conversation-scoped state accessors.");
}
// The dialogs will need a state store accessor. Creating it here once (on-demand) allows the dependency injection
// to hand it to our IBot class that is create per-request.
var accessors = new SimplePromptBotAccessors(conversationState)
{
ConversationDialogState = conversationState.CreateProperty<DialogState>("DialogState"),
};
return accessors;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
app.UseDefaultFiles()
.UseStaticFiles()
.UseBotFramework();
}
}
}
Here's a diff, so you can easily see the code differences.
Here's a screenshot of it working:
It looks like your code also stores the userState and conversationState in separate collections. I think that works...but the "conventional" method is to only create one instance of CosmosDbStorage. The bot will store userState and conversationState in separate documents within the collection. Note that in addition to the above code, you'll likely need something like, var userState = new UserState(_myStorage), since your code also uses userState and the above code does not.
Additionally, and in line with Drew's answer, I think the code from that tutorial you linked might be causing some issues, simply because it's out of date. The best thing to do, would be finding a relevant sample from the GitHub Repo and using that as a guide. Basic Bot is a good one with conversationState and userState functionality.
Your code makes it seem like maybe you were writing some code using the pre-release bits at one point or maybe copied from someone who was. ConversationState itself is no longer a piece of middleware, nor is it a generic class any longer.
So you no longer create a ConversationState<T> for each piece of state you want to maintain. Instead you create a single ConversationState, which acts as a sort of scoping "bucket" if you will, and then you create many properties within that "bucket" using the CreateProperty<T> API.
Like this:
var conversationState = new ConversationState(myStorage);
var myBasicStateProperty conversationState.CreateProperty<BasicState>("MyBasicState");
Now, as I also said, it is no longer middleware. Instead, what is returned from CreateProperty<T> is an IStatePropertyAccessor<T> which you can then pass into whatever needs to use it (e.g. your bot). Likewise you would also pass the ConversationState itself into the bot so it can ultimately call SaveChangesAsync on it at the end of the turn. Alternatively you can configure the AutoSaveStateMiddleware which will take care of saving the state for you at the end of every turn, but you lose control over the ability to deal with exceptions that arise during the call to SaveChangesAsync (e.g. network partition, data concurrency, etc).
I am using Enterprise library 4.1 logging. I am getting compile error at 'EnterpriseLibraryContainer'. EnterpriseLibraryContainer doesn't work for 4.1 version?
public LogWriter defaultWriter;
public Logging()
{
// Resolve the default LogWriter object from the container.
// The actual concrete type is determined by the configuration settings.
defaultWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
}
[Description("Logging to EverViewer and RollingFile with Write method of a LogWriter")]
public void LogWriter(string message, string title, EventLogEntryType eventType)
{
// Check if logging is enabled before creating log entries.
if (defaultWriter.IsLoggingEnabled())
{
// Create a string array (or List<>) containing the categories.
string[] logCategories = new string[] { "General" };
LogEntry logEntry = new LogEntry();
logEntry.Message = message;
logEntry.Categories = logCategories;
logEntry.Priority = 10;
logEntry.EventId = 9005;
logEntry.Severity = ConvertEventType(eventType);
logEntry.Title = title;
defaultWriter.Write(logEntry);
}
}
EnterpriseLibraryContainer was introduced with Enterprise Library 5 so is not available with Enterprise Library 4.1.
Try replacing that code with the following to get the default writer:
public Logging()
{
// Resolve the default LogWriter object from the container.
// The actual concrete type is determined by the configuration settings.
defaultWriter = new LogWriterFactory().Create();
}
Are you by any chance targeting the .NET 4 Client Profile instead of the full .NET 4?
Situation: I want to show the method and line number for the code that logs a message. The problem is that I have an intermediate class which calls into the log4net logger. Unfortunately, due to existing architectural issues, I can't do away with this intermediate class. The result is that I always see the method and line number as being in the intermediate class. Not what I want.
So I tried to create a custom PatternLayoutConverter, as per this question:
Does log4net support including the call stack in a log message
I am also programmatically configuring log4net since, again for architectural reasons, using an XML config file is not feasible (I also find it ridiculous that the preferred and only documented way of configuring log4net is through a stupid XML file, but that's a topic for another discussion). So I followed this thread.
How to configure log4net programmatically from scratch (no config)
Everything works fine except that my custom converter is never called. Here is the code for the custom converter:
public class TVPatternLayout : PatternLayout {
public TVPatternLayout() {
this.AddConverter("logsite", typeof(TVPatternLayoutConverter));
}
}
public class TVPatternLayoutConverter : PatternLayoutConverter {
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent) {
StackTrace st = new StackTrace();
int idx = 1;
while(st.GetFrame(idx).GetMethod().DeclaringType.Assembly == typeof(LogManager).Assembly)
idx++;
writer.Write(String.Format("{0}.{1} (line {2})", st.GetFrame(idx).GetMethod().DeclaringType.Name, st.GetFrame(idx).GetMethod().Name,
st.GetFrame(idx).GetFileLineNumber()));
}
}
And here is the code where I configure the logger:
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
hierarchy.Configured = false;
RollingFileAppender appender = new RollingFileAppender();
appender.Name = loggerName;
appender.File = realPath;
appender.AppendToFile = true;
appender.MaximumFileSize = "8000";
appender.MaxSizeRollBackups = 2;
TVPatternLayout patternLayout = new TVPatternLayout();
patternLayout.ConversionPattern = logFormat; // includes %logsite, my custom option
appender.Layout = patternLayout;
appender.ActivateOptions();
hierarchy.Root.AddAppender(appender);
hierarchy.Root.Level = Level.All;
hierarchy.Configured = true;
Problem was that I forgot to call ActivateOptions() on the patternLayout. Naturally, I'd figure that out right after writing up a long question.