Log Management
Server side (server side)
Boilerplate uses Castle Windsor's logging facility logging tool, and can use different log class libraries, such as: Log4Net, NLog, Serilog... and so on. For all log class libraries, Castle provides a general interface to implement it. We can easily handle various special log libraries, and it is easy to replace log components when business needs it.
Translator's Note: What is Castle: Castle is an open source project for the .NET platform, from data access framework ORM to IOC containers, to the MVC framework and AOP at the WEB layer, it basically includes everything in the entire development process. Boilerplate's ioc container is implemented through Castle.
Log4Net is the most popular log library component below. The Boilerplate template also uses the Log4Net log library component. However, we implement Log4Net dependency injection through just one line of key code (specifically explained in the configuration file below). Therefore, it is also easy to replace it with your own log component.
Get logger
Regardless of which log library component you choose, it is the same for logging through code. (Complaint here, Castle's universal ILogger interface is really awesome).
Let’s get to the topic: (Translator’s note: The following code is the source code analysis and implementation of the abp framework)
1. First of all, we need to deal with the logger object logger first. The Boilerplate framework uses dependency injection dependency injection technology, and we can easily use dependency injection to generate the logger object logger.
Next, let’s take a look at how Boilerplate implements logging function:
using ; //1: Import the namespace of the log,public class TaskAppService : ITaskAppService { //2: Get logger object through dependency injection. Here is a definitionILoggerType ofpublicpropertyLogger,This object is the object we use to record logs。CreatedTaskAppServiceObject(It is the task defined in our application)after,通过property注入的方式来实现。 public ILogger Logger { get; set; } public TaskAppService() { //3: If there is no logger, return the logger to an empty instance and do not write the log. This is the best way to implement dependency injection, // If you do not define this empty logger, an exception will be generated when we get the object reference and instantiate it. // Doing so ensures that the object is not empty. So, in other words, without setting up a logger, the log will not be recorded, and a null object will be returned. // The NullLogger object is actually nothing, empty. Only by doing this can we ensure that the classes we define work normally when instantiated. Logger = ; } public void CreateTask(CreateTaskInput input) { //4: Write to the log ("Creating a new task with description: " + ); //TODO: save task to database... } }
INFO 2014-07-13 13:40:23,360 [8 ] - Creating a new task with description:Remember to drink milk before sleeping!
After writing to the log, we can view the log file, just like the following format:
The Boilerplate framework provides the base classes of MVC Controllers, Web API Controllers and Application service classes (the controllers and application services you define yourself must inherit the base classes of Boilerplate. In other words, when your customized Web API controllers, mvc controllers, and Application service classes all inherit the base classes corresponding to the Boilerplate framework, you can directly use the logger).
public class HomeController : SimpleTaskSystemControllerBase { public ActionResult Index() { ("A sample log message..."); return View(); } }
Description: SimpleTaskSystemControllerBase This base class controller is the base class controller we define ourselves, and it must inherit from AbpController.
This way, the logger can work normally. Of course, you can also implement your own base class, so you can no longer use dependency injection.
Configuration
If you generate your project through Boilerplate templates on the official website, all configurations of Log4Net are automatically generated.
The default configuration format is as follows:
•Log level: Log recording level, 5 DEBUG, INFO, WARN, ERROR or FATAL.
•Date and time: Logging time.
•Thread number: The thread number when each line of log is written.
•Logger name: The name of the logger, usually the class name.
•Log text: The log content you wrote.
Configuration file: It is usually located in the project's web directory.
<?xml version="1.0" encoding="utf-8" ?> <log4net> <appender name="RollingFileAppender" type="" > <file value="Logs/" /> <appendToFile value="true" /> <rollingStyle value="Size" /> <maxSizeRollBackups value="10" /> <maximumFileSize value="10000KB" /> <staticLogFileName value="true" /> <layout type=""> <conversionPattern value="%-5level %date [%-5.5thread] %-40.40logger - %message%newline" /> </layout> </appender> <root> <appender-ref ref="RollingFileAppender" /> <level value="DEBUG" /> </root> <logger name="NHibernate"> <level value="WARN" /> </logger> </log4net>
Log4Net is a very powerful and easy-to-use log library component. You can write various logs, such as writing to txt files, writing to databases, etc. You can set the minimum log level, just like the above configuration for NHibernate. Different loggers write different logs, etc.
For specific usage, please refer to: /log4net/release/
Finally, in the project file, define the Log4Net configuration file:
public class MvcApplication : AbpWebApplication { protected override void Application_Start(object sender, EventArgs e) { <LoggingFacility>(f => f.UseLog4Net().WithConfig("")); base.Application_Start(sender, e); } }
A few lines of code call the Log4Net logging component. The Log4Net library in the project is in the nuget package. You can also change it to other log component libraries, but the code does not need to be changed. Because, our framework implements the logger through dependency injection!
Client side (client)
Finally, what's even more amazing is that you can also call the logger on the client side. On the client side, the Boilerplate framework has a corresponding javascript log API, which means that you can record the browser's logs, and the implementation code is as follows:
('a sample log message...');
Attached: Client JavaScript API, it is important to explain here that you can use it to output logs on the client side, but this API does not necessarily support all browsers, and may also cause exceptions to your script. You can use our API, ours is safe, and you can even overload or extend these APIs.
('...'); ('...'); ('...'); ('...'); ('...');
Settings Management
introduce
Each application needs to store some settings and use them somewhere in the application. The ABP framework provides a powerful infrastructure that we can set on the server or client to store/get application, tenant and user-level configurations.
Settings are usually stored in a database (or another source), represented by the structure corresponding to the name-value string. We can convert non-string values into string values for storage.
Note: About the ISettingStore interface
In order to use Settings Management, the ISettingStore interface must be implemented. You can implement it in your own way, and there are complete implementations in the module-zero project to refer to.
Define settings
You must define the settings before using them. The ABP framework is a modular design, so different modules can have different settings. To define the module's own settings, each module should create a derived class inherited from SettingProvider. The setup provider example is as follows:
public class MySettingProvider : SettingProvider { public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context) { return new[] { new SettingDefinition( "SmtpServerAddress", "127.0.0.1" ), new SettingDefinition( "PassiveUsersCanNotLogin", "true", scopes: | ), new SettingDefinition( "SiteColorPreference", "red", scopes: , isVisibleToClients: true ) }; } }
The GetSettingDefinitions method returns a SettingDefinition object. The constructor of the SettingDefinition class has the following parameters:
•Name (required): Must have a unique name on the entire system. A better way is to define string constants to set Name.
•Default value: Sets a default value. This value can be null or an empty string.
•Scopes: Defines the scope of the setting (see below).
•Display name: A localizable string for later displaying the set name in the UI.
•Description: A localizable string for later displaying the description of settings in the UI.
•Group: Can be used to set up groups. This is only for UI use and is not for setting management.
•IsVisibleToClients: Setting to true will make the settings available on the client.
After creating the SettingProvider, we should register our module in the PreIntialize method:
<MySettingProvider>(); Setting provider automatically registers dependency injection. Therefore, the setup provider can inject any dependencies (such as a repository) to generate some other source of the setup definition.
Set the range
There are three settings (or levels) defined in the SettingScopes enum:
•Application: Application-wide settings are used for user/tenant independent settings. For example, we can define a setting called "SmtpServerAddress" that gets the server's IP address when sending an email. If this setting has a single value (not changed based on user), then we can define it as application scope.
•Tenant: If the application is multi-tenant, we can define tenant-specific settings.
•User: We can use user-wide settings to store/get set values for each user.
The SettingScopes enum has a Flags property, so we can define a setting with multiple scopes.
The settings range is hierarchical. For example, if we define the setting range to "Application | Tenant | User" and try to get the value of the current setting;
•We get the value of a specific user if it defines (rewrites) User.
•If not, we get the specific tenant value if it defines (rewrites) Tenant.
•If not, we get the value of the application if it defines the Application.
•If not, we get the default value.
The default value can be null or an empty string. If possible, it is recommended to provide a default value for the settings.
Get the set value
After defining the settings, we can get its current value on the server and client.
(1) Server side
ISettingManager is used to perform settings. We can inject and use it anywhere in the application. ISettingManager defines many methods to get set values.
The most commonly used method is GetSettingValue (or GetSettingValueAsync is an asynchronous call). It returns the currently set value based on the default, application, tenant, and user settings range (as described in the paragraph before setting range). example:
//Getting a boolean value (async call) var value1 = await <bool>("PassiveUsersCanNotLogin"); //Getting a string value (sync call) var value2 = ("SmtpServerAddress");
GetSettingValue has generic and asynchronous versions, as shown above. There are also ways to get a list of settings for a specific tenant or user or all settings for it.
Because ISettingManager is widely used, some specific base classes (such as ApplicationService, DomainService, and AbpController) have a property called SettingManager. If we inherit from these classes, there is no need to inject it explicitly.
(2) Client
If IsVisibleToClients is set to true when defining the settings, you can get its current value using javascript on the client. The namespace defines the required functions and objects. Example:
var currentColor = ("SiteColorPreference"); There are also methods such as getInt and getBoolean. You can use the object to get all values. Note that if you change settings on the server side, the client won't know about this change unless the page is refreshed or reloaded in some way or updated manually via code.
Change settings
ISettingManager defines ChangeSettingForApplicationAsync, ChangeSettingForTenantAsync and ChangeSettingForUserAsync methods (and the synchronous version) to change the settings of the application, tenant, and user separately.
About Cache
The cache is in server-side settings management, so we should not directly use the repository or database update statement to change the set value.