I am working with Azure web jobs. Also I am aware that the TextWriter is used to write logs in case of web jobs (VS 2013). However, The logs are created under the Output logs folder under the blob container. THese are not user friendly. I have to open each file to read the message written to it.
Is there any way to change the logging to table, which is user friendly to read?
Thanks in advance.
I'm not sure if there's a "native" way to do this, but you can add Azure Storage Client through nuget and write your own "Log To Azure Tables".
You can use the Semantic Logging Application Block for Windows Azure.
It allows you to log into an Azure Table Storage.
Define your Eventsource:
// A simple interface to log what you need ...
public interface ILog
{
void Debug(string message);
void Info(string message);
void Warn(string message);
void Error(string message);
void Error(string message, Exception exception);
}
Implement the interface :
And the implementation ( implementation of your interface must be decorated with the NonEventAttribute see this post) :
[EventSource(Name = "MyLogEventsource")]
public class Log : EventSource, ILog
{
public Log()
{
EventSourceAnalyzer.InspectAll(this);
}
[NonEvent]
public void Debug(string message)
{
DebugInternal(message);
}
[Event(1)]
private void DebugInternal(string message)
{
WriteEvent(1, message);
}
[NonEvent]
public void Info(string message)
{
InfoInternal(message);
}
[Event(2)]
private void InfoInternal(string message)
{
WriteEvent(2, message);
}
[NonEvent]
public void Warn(string message)
{
WarnInternal(message);
}
[Event(3)]
private void WarnInternal(string message)
{
WriteEvent(3, message);
}
[NonEvent]
public void Error(string message)
{
ErrorInternal(message, "", "");
}
[NonEvent]
public void Error(string message, Exception exception)
{
ErrorInternal(message, exception.Message, exception.ToString());
}
[Event(4)]
private void ErrorInternal(string message, string exceptionMessage, string exceptionDetails)
{
WriteEvent(4, message, exceptionMessage, exceptionDetails);
}
}
Now you can register your event source like that :
var log = new Log();
var eventListeners = new List<ObservableEventListener>();
// Log to Azure Table
var azureListener = new ObservableEventListener();
azureListener.EnableEvents(log , EventLevel.LogAlways, Keywords.All);
azureListener.LogToWindowsAzureTable(
instanceName: Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID") ?? "DevelopmentInstance",
connectionString: CloudConfigurationManager.GetSetting("MyStorageConnectionString")
tableAddress: "MyLogTable");
eventListeners .Add(azureListener);
Related
I have an azure function, it logs the information without any issues.
namespace aspnetcore_azurefun_blob
{
[StorageAccount("AzureWebJobsStorage")]
public class FileTrigger
{
#region Property
private readonly IFileProcessor fileProcessor;
#endregion
#region Constructor
public FileTrigger(IFileProcessor fileProcessor)
{
this.fileProcessor = fileProcessor;
}
#endregion
[FunctionName("FileTrigger")]
public void ProcessFilesFromSamplesContainer([BlobTrigger("samples-workitems/{name}")]Stream myBlob, string name, ILogger log, ExecutionContext context)
{
log.LogInformation("Function: ProcessFilesFromSamplesContainer is called");
log.LogInformation($"C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes");
var result = fileProcessor.ProcessAsync(myBlob, name);
log.LogInformation($"Function Completed Successfully on {DateTime.Now.ToLongDateString()} # {DateTime.Now.ToShortTimeString()}.\n.");
}
However, I also have the business logic implemented using DI and below is the excerpt of the implementation.
ServiceBase.cs
namespace BusinessService.Services.Common
{
public abstract class ServiceBase<T>
{
public static AppDbContext AppDbContext;
public static ILogger<T> Logger { get; set; }
public static AppConfigurator Configurator { get; set; }
public ServiceBase(AppDbContext appDbContext, ILogger<T> logger, IOptions<AppConfigurator> configurator)
{
AppDbContext = appDbContext ?? throw new ArgumentNullException(nameof(appDbContext));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Configurator = configurator.Value ?? throw new ArgumentNullException(nameof(configurator));
}
}
}
FileProcessingService.cs
namespace BusinessService.Services
{
public interface IFileProcessingService
{
void Process(Stream myBlob, string name);
}
public class FileProcessingService : ServiceBase<FileProcessingService>, IFileProcessingService
{
#region Constructor
public FileProcessingService(AppDbContext appDbContext, ILogger<FileProcessingService> logger, IOptions<AppConfigurator> configurator)
: base(appDbContext, logger, configurator) { }
#endregion
#region Public Methods
public void Process(Stream myBlob, string name)
{
AppDbContext.FileRecords.Add(new FileRecords
{
FileName = name,
IsCompleted = DefaultValues.IsCompleted
});
AppDbContext.SaveChanges();
Logger.LogInformation("Reading configuration from the configuration settings file: {Configurator.AzureSQLServerConfigurator.ConnnectionString}");
Logger.LogInformation("Database is updated..!");
}
#endregion
}
}
Line#34 and #35 doesn't Log anything
Logger.LogInformation("Reading configuration from the configuration settings file: {Configurator.AzureSQLServerConfigurator.ConnnectionString}");
Logger.LogInformation("Database is updated..!");
DependencyRegistrar.cs
namespace CrossCutting.DependencyInjection
{
public static class DependencyRegistrar
{
public static void Intialize(this IServiceCollection services)
{
// Initialize App Settings from Configurator Settings Json file
services.AddOptions<AppConfigurator>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("AppConfigurator").Bind(settings);
})
.Validate((c) =>
{
return !new[] { c.AzureSQLServerConfigurator.ConnnectionString }.Any(s => String.IsNullOrWhiteSpace(s));
});
}
}
}
What am I missing so that FileProcessingService.cs will log the information ?
I have checked your code in our end I could get the logging information. In your code i have noticed in your code you are using the Logger instead of logger.
Because in your Region Constructor you are using ILogger<FileProcessingService> logger from here you have to call the logger to push your logging information into Application Insights/Function execution panel (Output console window)
#region Constructor
public FileProcessingService(AppDbContext appDbContext, ILogger<FileProcessingService> logger, IOptions<AppConfigurator> configurator)
: base(appDbContext, logger, configurator) { }
#endregion
#region Public Methods
public void Process(Stream myBlob, string name)
{
AppDbContext.FileRecords.Add(new FileRecords
{
FileName = name,
IsCompleted = DefaultValues.IsCompleted
});
AppDbContext.SaveChanges();
# changed Logger into logger
logger.LogInformation("Reading configuration from the configuration settings file: {Configurator.AzureSQLServerConfigurator.ConnnectionString}");
logger.LogInformation("Database is updated..!");
}
#endregion
Still if not able to push the logs you can add your namespace in your host.json file to avoid missing the logging
{
"version": "2.0",
"logging": {
"logLevel": {
// Here you can use your Project namespace like BusinessService.Services
"<namespace>": "Information"
}
}
}
After I send a message to a topic on Azure Service Bus using Spring Integration I would like to get the message id Azure generates. I can do this using JMS. Is there a way to do this using Spring Integration? The code I'm working with:
#Service
public class ServiceBusDemo {
private static final String OUTPUT_CHANNEL = "topic.output";
private static final String TOPIC_NAME = "my_topic";
#Autowired
TopicOutboundGateway messagingGateway;
public String send(String message) {
// How can I get the Azure message id after sending here?
this.messagingGateway.send(message);
return message;
}
#Bean
#ServiceActivator(inputChannel = OUTPUT_CHANNEL)
public MessageHandler topicMessageSender(ServiceBusTopicOperation topicOperation) {
DefaultMessageHandler handler = new DefaultMessageHandler(TOPIC_NAME, topicOperation);
handler.setSendCallback(new ListenableFutureCallback<>() {
#Override
public void onSuccess(Void result) {
System.out.println("Message was sent successfully to service bus.");
}
#Override
public void onFailure(Throwable ex) {
System.out.println("There was an error sending the message to service bus.");
}
});
return handler;
}
#MessagingGateway(defaultRequestChannel = OUTPUT_CHANNEL)
public interface TopicOutboundGateway {
void send(String text);
}
}
You could use ChannelInterceptor to get message headers:
public class CustomChannelInterceptor implements ChannelInterceptor {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
//key of the message-id header is not stable, you should add logic here to check which header key should be used here.
//ref: https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/spring/azure-spring-cloud-starter-servicebus#support-for-service-bus-message-headers-and-properties
String messageId = message.getHeaders().get("message-id-header-key").toString();
return ChannelInterceptor.super.preSend(message, channel);
}
}
Then in the configuration, set this interceptor to your channel
#Bean(name = OUTPUT_CHANNEL)
public BroadcastCapableChannel pubSubChannel() {
PublishSubscribeChannel channel = new PublishSubscribeChannel();
channel.setInterceptors(Arrays.asList(new CustomChannelInterceptor()));
return channel;
}
how to integrate an offline map? am trying this?
private class DownloadTask extends AsyncTask<String, Void, String> {
// Downloading data in non-ui thread
#Override
protected String doInBackground(String... url) {
// For storing data from web service
String data = "";
try {
// Fetching the data from web service
data = downloadUrl(url[0]);
Log.d("DownloadTask", "DownloadTask : " + data);
} catch (Exception e) {
Log.d("Background Task", e.toString());
}
return data;
}
// Executes in UI thread, after the execution of
// doInBackground()
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
ParserTask parserTask = new ParserTask();
// Invokes the thread for parsing the JSON data
parserTask.execute(result);
}
}
you can use mapbox, openstreetMap or osmdroid!
https://github.com/osmdroid/osmdroid
https://docs.mapbox.com/android/maps/guides/offline/
https://www.openstreetmap.org/`enter code here`
I have a simple Azure Function which returns to a queue:
private readonly TelemetryClient _telemetryClient;
[return: Queue("%ReturnQueue%")]
public async Task<string> Run([QueueTrigger("%RequestQueue%")] string msg, ILogger log)
{
try
{
//Some dependency calls
}
catch(Exception ex)
{
var dic = new Dictionary<string,string>();
dic.Add("Id", someId);
dic.Add("CustomData", cusomData);
_telemetryClient.TrackException(ex, dic);
}
}
I obviously get a compilation error saying that not all code paths returns a value.
The problem is that if I add a throw at the end of the catch block the Azure Functions runtime replicate the excpetion on the appinsights portal. How can I add custom data to my exceptions like this?
You can create your own Exception type:
public class MyCustomException : Exception
{
public string Id {get;set;}
public string CustomData {get;set;}
public Exception RootException {get;set;}
public MyCustomException(string id, string customData, Exception ex)
{
Id = id;
CustomData = customData;
RootException = ex;
}
}
private readonly TelemetryClient _telemetryClient;
[return: Queue("%ReturnQueue%")]
public async Task<string> Run([QueueTrigger("%RequestQueue%")] string msg, ILogger log)
{
try
{
//Some dependency calls
}
catch(Exception ex)
{
//var dic = new Dictionary<string,string>();
//dic.Add("Id", someId);
//dic.Add("CustomData", cusomData);
var customEx = new MyCustomException(someId, cusomData, ex);
_telemetryClient.TrackException(customEx);
}
finally
{
return "";
}
}
PS: inside MyCustomException you can actually have Dictionary rather than string properties.
Do You have any idea how to log all outgoing/incoming messages? I am not sure how to capture outgoing messages.
I use Chains and Forms.
For example
await Conversation.SendAsync(activity, rootDialog.BuildChain);
AND
activity.CreateReply(.....);
I found better solution
public class BotToUserLogger : IBotToUser
{
private readonly IMessageActivity _toBot;
private readonly IConnectorClient _client;
public BotToUserLogger(IMessageActivity toBot, IConnectorClient client)
{
SetField.NotNull(out _toBot, nameof(toBot), toBot);
SetField.NotNull(out _client, nameof(client), client);
}
public IMessageActivity MakeMessage()
{
var toBotActivity = (Activity)_toBot;
return toBotActivity.CreateReply();
}
public async Task PostAsync(IMessageActivity message, CancellationToken cancellationToken = default(CancellationToken))
{
await _client.Conversations.ReplyToActivityAsync((Activity)message, cancellationToken);
}
}
public class BotToUserDatabaseWriter : IBotToUser
{
private readonly IBotToUser _inner;
public BotToUserDatabaseWriter(IBotToUser inner)
{
SetField.NotNull(out _inner, nameof(inner), inner);
}
public IMessageActivity MakeMessage()
{
return _inner.MakeMessage();
}
public async Task PostAsync(IMessageActivity message, CancellationToken cancellationToken = default(CancellationToken))
{
// loging outgoing message
Debug.WriteLine(message.Text);
//TODO log message for example into DB
await _inner.PostAsync(message, cancellationToken);
}
In controller use
public MessagesController()
{
var builder = new ContainerBuilder();
builder.RegisterType<BotToUserLogger>()
.AsSelf()
.InstancePerLifetimeScope();
builder.Register(c => new BotToUserTextWriter(c.Resolve<BotToUserLogger>()))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.Update(Microsoft.Bot.Builder.Dialogs.Conversation.Container);
}
Its look like I cant log outgoing message.
I changed SDK source code.
Add event in Conversations.cs
For example like this.
public delegate void MessageSendedEventHandler(object sender, Activity activity, string conversationId);
public static event MessageSendedEventHandler MessageSended;
And add in every Send....HttpMessagesAsync method
this MessageSended?.Invoke(this, activity, conversationId);
its not great solution. But its working