How to get complete Log in Azure Function? - .net-core

I try to get the complete log in my Azure function, including ALL the custom log I added in my code, currently I only have something like:
2020-11-28T00:56:59.614 [Information] Executing 'test-log' (Reason='This
function was programmatically called via the host APIs.',
Id=7f82a0c4-5ae9-416c-8f19-9c00722ded2f) 2020-11-28T00:56:59.848
[Information] Executed 'test-log' (Succeeded,
Id=7f82a0c4-5ae9-416c-8f19-9c00722ded2f, Duration=247ms)
Here is how I configure the azure function:
public class Startup : FunctionsStartup
{
public IConfiguration Configuration { get; private set; }
public Startup() { }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services
.AddMvcCore()
.AddNewtonsoftJson(jsonOptions =>
{
jsonOptions.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});
builder.AddConfiguration((configBuilder) => configBuilder
.AddEnvironmentVariables()
.Build()
);
builder.Services.AddLogging();
Configuration = builder.GetCurrentConfiguration();
// explicitly call ConfigureServices to setup DI
ConfigureServices(builder.Services);
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(ILoggerService<>), typeof(LoggerService<>));
services.AddTelemetryClient(Configuration);
}
My LoggerService:
public class LoggerService<T> : ILoggerService<T> where T : class
{
private readonly TelemetryClient _telemetryClient;
private readonly ILogger<T> _logger;
// Flag: Has Dispose already been called?
bool disposed = false;
public LoggerService(
ILogger<T> logger
)
{
_telemetryClient = new TelemetryClient(TelemetryConfiguration.CreateDefault());
_logger = logger;
}
public void LogTrace(string message)
{
_logger.LogTrace(message);
}
public void LogTrace(string message, IDictionary<string, string> properties = null)
{
TrackMessage(message, LogLevel.Trace, properties);
LogTrace(message);
}
private void TrackMessage(string message, LogLevel logLevel, IDictionary<string, string> properties = null)
{
_telemetryClient.TrackTrace($"{logLevel}:{message}", properties);
Flush();
}
[...]
And My function:
public class MyFunctions : BaseFunctions
{
internal readonly ILoggerService _logger;
public POSFunctions(
ILoggerService<POSFunctions> logger
)
{
_logger= logger;
}
[FunctionName("test-log")]
public async Task<IActionResult> TestLogAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest request
)
{
_logger.LogInformation("This is log");
}
And in the Configuration of my Azure function I have:
Why I can not see "This is log" in the Azure Function Log?

The solution was to add in the host.json file:
"logging": {
"fileLoggingMode": "always",
"logLevel": {
"default": "Information"
},
"applicationInsights": {
"enableLiveMetrics": true,
"enableDependencyTracking": true,
}
},

Related

Asp.Net Core Web API entity Framework connect to two databases

I am doing an Asp.Net Core API and I am connecting to a two databases using EF setted in appsettings.json
"ConnectionStrings": {
"DBConnection": "Server=2679; Database=A; Trusted_Connection=true; MultipleActiveResultSets=true; Integrated Security=true;Encrypt=false;",
"DBConnection2": "Server= 2684; Database=B; Trusted_Connection=true; MultipleActiveResultSets=true; Integrated Security=true;Encrypt=false;"
}
In my Program.cs I have setted this two connections
var connectionString = (builder.Configuration.GetConnectionString("DBConnection") ?? String.Empty).Trim();
var connectionString2 = (builder.Configuration.GetConnectionString("DBConnectionAnthem") ?? String.Empty).Trim();
builder.Services.ConfigureServices(connectionString);
builder.Services.ConfigureServices(connectionString2);
I call ConfigureServices with both connections and looks like this
public static class Configure
{
public static void ConfigureServices(this IServiceCollection services, string connectionString)
{
services
.AddDbContext<CobraDbContext>(options => options.UseSqlServer(connectionString));
........
services.AddScoped<IUnitOfWork, UnitOfWork>();
}
}
}
I am using EF and I have defined my DbContext like this
public class CobraDbContext : DbContext
{
public CobraDbContext(DbContextOptions<CobraDbContext> options)
: base(options)
{
}
public DbSet<SearchResultModel> ParticipantSearch { get; set; } = null!;
....
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
}
From My Controller Method I call the Service.cs witch use UnitOfwork
public class ParticipantService : IParticipantService
{
private readonly ILogger<ParticipantService> _logger;
private readonly IUnitOfWork _iUnitOfwork;
public ParticipantService(ILogger<ParticipantService> logger, IUnitOfWork iUnitOfwork)
{
_logger = logger;
_iUnitOfwork = iUnitOfwork;
}
public async Task<HttpResponseMessage> Search(string participantId)
{
try
{
List<SearchResultModel>? search = await _iUnitOfwork.Participant.AAA(participantId);
return Request.CreateResponse(HttpStatusCode.OK, search);
}
catch (Exception ex)
{
}
}
From My Service I call the Repository that have a generic repository
public class ParticipantRepository : GenericRepository<SearchResultModel>, IParticipantRepository
{
private readonly CobraDbContext _db;
public ParticipantRepository(CobraDbContext db) : base(db)
{
_db = db;
}
public async Task<List<ParticipantPlanModel>?> AAA(string participantId)
{
Query participantGetByID = new();
Dictionary<string, string> dictionary = new Dictionary<string, string>();
participantGetByID.SelectFrom = " exec sp";
List<ParticipantPlanModel>? _return = await ExecuteGeneric(participantGetByID);
return _return;
}
}
I have my generic repo like this
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected readonly CobraDbContext Context;
internal DbSet<T> dbSet;
public GenericRepository(CobraDbContext context)
{
Context = context;
dbSet = context.Set<T>();
}
public async Task<List<T>?> ExecuteGeneric(Query query)
{
// var defaultVal = default(T);
var cParameters = new SqlParameter[query.Parameters?.Count ?? 0];
if (query.Parameters != null)
{
int i = 0;
foreach (KeyValuePair<string, string> _param in query.Parameters)
{
cParameters[i] = new SqlParameter() { ParameterName = _param.Key, Value = _param.Value };
i++;
}
}
return await Context.Set<T>().FromSqlRaw(query.SelectFrom + query.Where + query.OrderBy, cParameters).ToListAsync();
}
Depending on the parameter I have to call a database or a the another. I know I can do this duplicating almost all the code... Having to DbContext and two generic Repo..
Is there a way to simplify it and not replicate most of the code?
Thanks

How to get an email provider into a logger using DI in ASP.NET Core?

Sorry this is a bit new to me so I don't quite 'get it'.
I already have a logging provider
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
{
var loggingSection = Configuration.GetSection("Logging");
loggingBuilder.AddFile(loggingSection);
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
I am using the package NReco.Logging.File to define AddFile etc.
I want to make it so that exceptions are emailed to me too. So I followed https://learn.microsoft.com/en-us/dotnet/core/extensions/custom-logging-provider to create a custom logger.
public sealed class EmailLoggerConfiguration
{
public int EventId { get; set; }
public string EmailToSendTo { get; set; }
public IEmailSender EmailSender { get; set; }
}
internal class EmailLoggingProvider : ILoggerProvider
{
private readonly IDisposable? _onChangeToken;
private EmailLoggerConfiguration _currentConfig;
private readonly ConcurrentDictionary<string, EmailLogger> _loggers =
new(StringComparer.OrdinalIgnoreCase);
private readonly IEmailSender emailSender;
public EmailLoggingProvider(
IOptionsMonitor<EmailLoggerConfiguration> config)
{
_currentConfig = config.CurrentValue;
_onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
}
public ILogger CreateLogger(string categoryName) =>
_loggers.GetOrAdd(categoryName, name => new EmailLogger(name, GetCurrentConfig ));
private EmailLoggerConfiguration GetCurrentConfig() => _currentConfig;
public void Dispose()
{
_loggers.Clear();
_onChangeToken?.Dispose();
}
}
internal class EmailLogger : ILogger
{
private readonly string categoryName;
private Func<EmailLoggerConfiguration> getCurrentConfig;
IEmailSender emailSender;
public EmailLogger(string categoryName, Func<EmailLoggerConfiguration> getCurrentConfig)
{
this.getCurrentConfig = getCurrentConfig;
this.categoryName = categoryName;
}
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;
public bool IsEnabled(LogLevel logLevel) => !String.IsNullOrEmpty(getCurrentConfig().EmailToSendTo);
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var emailTo = getCurrentConfig().EmailToSendTo;
//var emailServer = getCurrentConfig().EmailSender;
if (!String.IsNullOrEmpty(emailTo) && exception != null)
{
emailSender.SendEmailAsync(emailTo, "Admin exception", exception.ToString());
}
}
}
public static class EmailLoggingExtensions
{
public static ILoggingBuilder AddEmailLogger(
this ILoggingBuilder builder)
{
builder.AddConfiguration();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, EmailLoggingProvider>());
LoggerProviderOptions.RegisterProviderOptions<EmailLoggerConfiguration, EmailLoggingProvider>(builder.Services);
return builder;
}
public static ILoggingBuilder AddEmailLogger(
this ILoggingBuilder builder,
Action<EmailLoggerConfiguration> configure)
{
builder.AddEmailLogger();
builder.Services.Configure(configure);
return builder;
}
}
You can see that EmailLogger.Log requires emailSender which should be an IEmailSender but I cannot figure out how to get it there using DI.
I realise that you can chain dependencies in DI but ???? I don't see how in this context.
I tried this
loggingBuilder.AddEmailLogger(c =>
{
c.EmailToSendTo = Configuration["Logging:Email:EmailToSendTo"];
c.EmailSender = new AuthMessageSender(????, Configuration);
});
but that didn't help and wouldn't even be right anyway.
In fact, by default, EmailSender is the implementation method of IEmailSender, which is used to call the SendEmailAsync() method. You don't need to go and set c.EmailSender = xxx.
You can consider the following dependency injection approach:
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
public class EmailSender : IEmailSender
{
//...
private readonly ILogger<EmailSender> logger;
public EmailSender(ILogger<EmailSender> logger) {
//...
this.logger = logger;
}
public Task SendEmailAsync(string email, string subject, string message) {
//...
}
}
At this point, IEmailSender will exist as a custom interface instead of inheriting from Microsoft.AspNetCore.Identity.UI.Services.
And you need to register it as a service:
services.AddTransient<IEmailSender, EmailSender>();
Helpful links:
Add ILogger to send email service
Should I use IEmailSender?
Using IEmailSender from Configure() in my Startup.cs file
Hope this will help you better understand IEmailSender and dependency injection.

How can I use Configuration in Net Core 6 worker app?

All information about using Configuation starts with creating builder and
var builder = WebApplication.CreateBuilder(args);
subsequnetly using
builder.Configuration.
configuration ,but in Worker services WebApplication is not available.
How can i use Configuration in Microsoft.NET.Sdk.Worker type of project?
This might help https://medium.com/c-sharp-progarmming/how-to-set-appsettings-or-config-in-a-net-worker-service-cc2d70ab4e0c
It is by accessing the HostBuilderContext
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
IConfiguration configuration = hostContext.Configuration;
services.Configure<RabbitMQConfiguration>(configuration.GetSection(nameof(RabbitMQConfiguration)));
services.AddHostedService<Worker>();
});
First of all, you should add ConfigureAppConfiguration when CreateHostBuilder
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
})
.ConfigureAppConfiguration((hostContext, configBuilder) =>
{
configBuilder
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.Build();
});
Then, add configuration in appsettings.YourEnv.json
"worker": {
"id": 12345,
"name": "SimpleWorker",
"delay": 1000
}
Add IConfiguration in Worker constructor and use it.
Full example of Worker class:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IConfiguration _configuration;
public Worker(ILogger<Worker> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var workerConfig = _configuration.GetSection("worker").Get<SimpleWorkerConfig>();
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker {id}:{name} running at: {time} with delay {delay}",
workerConfig.Id, workerConfig.Name, DateTimeOffset.Now, workerConfig.Delay);
await Task.Delay(workerConfig.Delay, stoppingToken);
}
}
}
public class SimpleWorkerConfig
{
public int Id { get; set; }
public string Name { get; set; }
public int Delay { get; set; }
}
Result:

InvalidOperationException: Unable to resolve service for type'Microsoft.EntityFrameworkCore.DbContextOptions`1

I need to create login with web api but when i enter this url https://localhost:44366/api/login show me this error :
InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions`1[IRI.DataLayer.Context.ApplicationDbContext]' while attempting to activate 'IRI.DataLayer.Context.ApplicationDbContext'.
and then enter the URL https://localhost:44366/api/login/Authenticate
show me this error
This localhost page can’t be found No webpage was found for the web address: https://localhost:44366/api/login/Authenticate
HTTP ERROR 404
what's the problem ? how can I solve this problem?
My code =>
LoginController :
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
private readonly IApplicationUserManager _userManager;
private readonly IApplicationSignInManager _signIn;
private readonly IOptionsSnapshot<SiteSetting> _options;
private readonly ILogger<LoginController> _logger;
public LoginController(IApplicationUserManager userManager
, IApplicationSignInManager signIn
, IOptionsSnapshot<SiteSetting> options
, ILogger<LoginController> logger)
{
_userManager = userManager;
_userManager.CheckArgumentIsNull(nameof(_userManager));
_options = options;
_options.CheckArgumentIsNull(nameof(_options));
_signIn = signIn;
_signIn.CheckArgumentIsNull(nameof(_signIn));
_logger = logger;
_logger.CheckArgumentIsNull(nameof(_logger));
}
public async Task<IActionResult> Authenticate(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.username);
if (user == null)
{
return BadRequest(Messages.IncorrectUsernamePassword);
}
if (!user.IsActive)
{
return BadRequest(Messages.NotActive);
}
if (_options.Value.EnableEmailConfirmation
&& await _userManager.IsEmailConfirmedAsync(user))
{
return BadRequest(Messages.EmailConfirmation);
}
}
var result = await _signIn.PasswordSignInAsync(
model.username,
model.password,
model.rememberme,
lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation(1, $"{model.username} logged in");
return Ok(User);
}
if (result.RequiresTwoFactor)
{
//TODO Create Function for TowFactor
}
if (result.IsNotAllowed)
{
return BadRequest(Messages.NotAllowed);
}
if (result.IsLockedOut)
{
_logger.LogWarning(2, $"{model.username} قفل شده‌است.");
return BadRequest(Messages.IsLocked);
}
return BadRequest(Messages.IncorrectUsernamePassword);
}
}
}
Startup :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddCustomServices();
}
AddCustomServices:
public static IServiceCollection AddCustomServices(this IServiceCollection services)
{
services.AddScoped<IUnitOfWork, ApplicationDbContext>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IPrincipal>(provider =>
provider.GetService<IHttpContextAccessor>()?.HttpContext?.User ?? ClaimsPrincipal.Current);
services.AddScoped<IApplicationSignInManager, ApplicationSignInManager>();
services.AddScoped<SignInManager<User>, ApplicationSignInManager>();
services.AddScoped<IApplicationUserManager, ApplicationUserManager>();
services.AddScoped<UserManager<User>, ApplicationUserManager>();
services.AddScoped<IApplicationUserStore, ApplicationUserStore>();
services.AddScoped<UserStore<User, Role, ApplicationDbContext, int, UserClaim, UserRole, UserLogin, UserToken, RoleClaim>, ApplicationUserStore>();
return services;
}
You have not configured DbContext in your application.
Add IdentityDbContext to your application :
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
}
}
And then register it in ConfigureServices :
services.AddDbContextPool<ApplicationDbContext>(opt => opt.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
And appSettings.json where ConnectionStrings are defined :
{
"ConnectionStrings": {
"DefaultConnection": "Server=Server-Name; Database=DBName; Trusted_Connection=True; MultipleActiveResultSets=True;"
}
}

Retrieving connection string in integration test

I am trying to do some integration tests for my asp.net Core 2.1 project.
I initialise on startup file my connection string but when i run the test it still empty in handler, what is wrong on my code?
[TestMethod]
public async Task Method1()
{
var webHostBuilder = new WebHostBuilder()
.UseEnvironment("Development")
.UseStartup<Startup>();
HttpRequestMessage getRequest = new HttpRequestMessage(HttpMethod.Get, "api/action")
{
};
getRequest.Headers.Add("userId", "4622");
getRequest.Headers.Add("clientId", "889");
using (var server = new TestServer(webHostBuilder))
using (var client = server.CreateClient())
{
var result = await client.SendAsync(getRequest);
...
}
}
Startup
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<SqlConfig>(options =>
{
options.ConnectionString = Configuration.GetConnectionString("DefaultConnection");
});
...
}
SqlConfig
public class SqlConfig
{
public string ConnectionString { get; set; }
}
Repository
public abstract class SqlServerQueryHandler<TQuery, TResult> : BaseQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
public SqlServerQueryHandler(IOptions<SqlConfig> connectionString)
{
this.ConnectionString = connectionString.Value.ConnectionString;
}
protected string ConnectionString { get; }
}
what solve my probleme is the following code :
public class TestStartup : Startup
{
public TestStartup(IConfiguration configuration, IHostingEnvironment env) : base(configuration)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
Include the appsettings.json via interface on the output directory

Resources