2019年1月27日 星期日

[Flask] Logging


 Python   Flask    Logging



Introduction


We are going to apply logging to Flask app.
The loggers will include:
1.  Application-level logger
2.  Custom logger





Environment

Python 3.6.5
Flask 1.0.2
Flask-SocketIO 3.1.2
Socket.IO 2.2.0
Vue.js 2.5.21
Vue-Socket.io 3.0.4


Implement



Create logger and its handlers

Logger object

Loggers are never instantiated directly.
The proper way is get/create it by using module-level function: logging.getLogger(name).
Multiple calls to getLogger() with the same name will always return a reference to the same Logger object.

import logging
my_logger = logging.getLogger("myLoggerName")


File handler

logging_format = logging.Formatter('%(asctime)s - %(thread)s - %(levelname)s - %(module)s.%(funcName)s(%(filename)s, %(lineno)s) - %(message)s')
 logfiles_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logs"))
 handler = logging.FileHandler('{0}\\mylog.log'.format(logfiles_path), encoding='UTF-8')
 handler.setLevel(logging.DEBUG) # Set handler's log level
 handler.setFormatter(logging_format) # Set format
 my_logger.addHandler(handler)

The above code created a logging.FileHandler object which was set log level and format.
Then we can add the handler to our logger.

Here are the log levels and their representative numeric value.
In the example, the handler will deal with the logs with levels whose numeric value are equals or higher than “DEBUG”.

Level
Numeric value
CRITICAL
50
ERROR
40
WARNING
30
INFO
20
DEBUG
10
NOTSET
0
(Reference: Logging Levels)


For logger, we can set the log level as well like following.
The logs with non-scoped levels will be ignored.

 my_ogger.setLevel(logging.INFO)


Start logging

my_logger.debug("[Debug]..")
my_logger.info("[INFO]...")
my_logger.warning("[WARNING]...")
my_logger.error("[ERROR]...")
my_logger.critical("[CRITICAL]...")


Result

The logs will be keeped in to logs/mylog.log




And the logs,

2019-01-27 12:13:08,750 - 13660 - DEBUG - main.<module>(main.py, 35) - [Debug]..
2019-01-27 12:13:08,750 - 13660 - INFO - main.<module>(main.py, 36) - [INFO]...
2019-01-27 12:13:08,750 - 13660 - WARNING - main.<module>(main.py, 37) - [WARNING]...
2019-01-27 12:13:08,751 - 13660 - ERROR - main.<module>(main.py, 38) - [ERROR]...
2019-01-27 12:13:08,751 - 13660 - CRITICAL - main.<module>(main.py, 39) - [CRITICAL]...



(Optional) Create a module to add handlers for logger

Since we can have multiple file handers to handle different levels, formatted logs.
Here is an sample code for writing a module for this,

logger_config.py

import sys
import os
import logging
from datetime import date

def init_logging(logger, min_logging_level):
     # Add DEBUG handler
     add_handler(logger, logging.DEBUG)
     # Add ERROR handler
     add_handler(logger, logging.ERROR)
     # Set level for this logger
     logger.setLevel(min_logging_level)

def add_handler(logger:logging.Logger, level):
     level_name = logging.getLevelName(level).lower()
     logging_format = logging.Formatter('[%(asctime)s][%(thread)s][%(levelname)s][%(module)s.%(funcName)s(%(filename)s, %(lineno)s)]%(message)s')
     logfiles_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logs", str(date.today()))
     create_log_dir(logfiles_path)
     handler = logging.FileHandler('{0}\\mylog.{1}.log'.format(logfiles_path, level_name), encoding='UTF-8')
     handler.setLevel(level) # Set handler's log level
     handler.setFormatter(logging_format) # Set format
     logger.addHandler(handler)

def create_log_dir(directory):
     if not os.path.exists(directory):
         os.makedirs(directory)


So when I call the init_logging method, the application will stores the logs as:

logs/2019-01-27/mylog.bug.log
logs/2019-01-27/mylog.error.log


Flask application logger

Flask uses standard Python logging.
We can get the Flask application-level logger by app.logger as following,

from flask import Flask, render_template, request, jsonify
 from modules.logger_config import init_logging

app = Flask(__name__)
init_logging(app.logger, logging.DEBUG)


and logs like this,

app.logger.error("[ERROR]...")


Notice that the app.logger’s logs will also be shown on the console window.
So if you don’t want to show the Flask app.logger’s logs on console, you can disable it by setting the log levels to a number greater than 50 (CRITICAL) :

app.logger.setLevel(100)


Sample code





Reference






2019年1月25日 星期五

[moq] Mock delegate


 C#   Unit test   moq   delegate


Introduction


This article shows how to mock delegate for unit-testing.




Environment


Visual Studio 2017 community
dotnet core 2.2.101
Moq 4.10.1


Implement


We will test the Get(…) function of class, DataAccessService<T> which implements interface, IDataAccessService<T>.  It has a delegate, AuthorizeEvent, which we will mock in this test.

The unit test’s goal:
1.  Test Get(…) function’s accuracy
2.  The delegate is called

Interfaces and Classes

IDataAccessService.cs

public delegate bool Authorize(string caller);

  public interface IDataAccessService<T>
  {
        string ConnectionStr { get; set; }
        event Authorize AuthorizeEvent;
        T Get(string id);
  }



DataAccessService.cs

public class DataAccessService<T> : IDataAccessService<T> where T:new()
    {
        private readonly string _caller = "DataAccessService";
        public string ConnectionStr { get; set; }
        public event Authorize AuthorizeEvent;

        public DataAccessService()
        {

        }
        public T Get(string id)
        {
            //HACK: Implement the logic here to replace the below line
                if (this.AuthorizeEvent(this._caller) == true)
                    return new T();
                else
                    return default(T);
        }
    }


Unit test without mocking the delegate

Lets see a bad idea of not mocking the delegate in the unit test.
That means we have to create a method as a reference for the delegate, and a static flag to make sure the delegate is triggered.


private static bool callbackIsTriggered = false;

[Test]
  public void TestGet()
  {
            IDataAccessService<Birthday> dataAccess = new DataAccessService<Birthday>();

            #region Use the real method
            dataAccess.AuthorizeEvent += MySaveLogAction;
            var rtn = dataAccess.Get(It.IsAny<string>());
            Assert.IsTrue(callbackIsTriggered);
            Assert.IsFalse(rtn == null);
            #endregion
 }

 private static bool MySaveLogAction(string caller)
 {
            Debug.WriteLine($"Called from {caller}");
            callbackIsTriggered = true;
            return true;
 }
    }




Unit test by mocking delegate with moq

We will use moq to mock the delegate, so that we can focus on tesing the IDataAccessService.Get() function.

[Test]
  public void TestGet()
  {
            IDataAccessService<Birthday> dataAccess = new DataAccessService<Birthday>();

            #region Mock the callback
            var mockCallback = new Mock<Authorize>();
            mockCallback.Setup(x => x(It.IsAny<string>())).Returns(true);
            dataAccess.AuthorizeEvent += mockCallback.Object;
            dataAccess.Get(It.IsAny<string>());
            mockCallback.Verify(x => x(It.IsAny<string>()), Times.Once);
Assert.IsFalse(rtn == null);
            #endregion
  }

The above code mocks the delegate: Authorize, and setup the returning value. (If the delegate returns nothing, then ignore .Returns(…)) in this line:

mockCallback.Setup(x => x(It.IsAny<string>())).Returns(true);

And notice we CANNOT directly mock the Invoke method of a delegate like this:
 mockCallback.Setup(x => x.Invoke(It.IsAny<string>())).Returns(true);
Which will cause the error:
System.InvalidCastException : Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression1' to type 'System.Linq.Expressions.InvocationExpression'.



Reference