log4net 动态载入配置根据类名进行写入日志文件

前言

默认log4net配置写入的日志文件都是写入到一个日志文件当中,最多根据时间进行拆分文件,当日志过多时不好排除问题,单个文件也会过大

实现方式

using log4net.Appender;
using log4net.Core;
using log4net.Layout;
using log4net;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


/// <summary>
/// 动态生成日志配置文件,根据传入的Type,该类不影响默认 log4net.config 配置文件
/// </summary>
public static class LoggerManager
{
    public static ILog GetLogger(Type type)
    {
        DateTime Now = DateTime.Now;
        // All Level 日志存储位置
        string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", Now.ToString("yyyy-MM-dd"), $"{type.Name}.log");
        // 异常 Error Level 日志存储位置
        string logErrorFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", "Error",Now.ToString("yyyy-MM-dd"), $"{type.Name}.log");
        var hierarchy = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
        var logger = hierarchy.GetLogger(type.FullName) as log4net.Repository.Hierarchy.Logger;

        if (logger != null && logger.Appenders.Count > 0)
        {
            return new LogImpl(logger);
        }

        // 全局等级的配置  RollingFileAppender
        var appender = new RollingFileAppender
        {
            Name = $"{type.Name}Appender",
            File = logFilePath,
            AppendToFile = true,
            RollingStyle = RollingFileAppender.RollingMode.Size,
            MaxFileSize = 10 * 1024 * 1024, // 10MB
            MaxSizeRollBackups = 5,
            StaticLogFileName = true,
            Layout = new PatternLayout("[%date{yyyy-MM-dd HH:mm:ss,fff}] [%level] %message%newline"),
            Threshold = Level.All,
            LockingModel = new FileAppender.MinimalLock(),
            Encoding = Encoding.UTF8
        };

        // 异常的配置
        var errAppender = new RollingFileAppender
        {
            Name = $"{type.Name}Appender",
            File = logErrorFilePath,
            AppendToFile = true,
            RollingStyle = RollingFileAppender.RollingMode.Size,
            MaxFileSize = 10 * 1024 * 1024, // 10MB
            MaxSizeRollBackups = 5,
            StaticLogFileName = true,
            Layout = new PatternLayout("[%date{yyyy-MM-dd HH:mm:ss,fff}] [%level] %message%newline"),
            Threshold = Level.Error,
            LockingModel = new FileAppender.MinimalLock(),
            Encoding = Encoding.UTF8
        };

        appender.ActivateOptions();
        errAppender.ActivateOptions();
        logger.AddAppender(appender);
        logger.AddAppender(errAppender);

        logger.Repository.Configured = true;

        return new LogImpl(logger);
    }
}



上述方法可以在不允许原有配置log4net.config 情况下进行

使用案例

下面代码是SuperSocket的一个命令(Socket)接收方法
private static readonly log4net.ILog sg_log = LoggerManager.GetLogger(typeof(DevAlarmReport));
使用动态记录

记录Info日志
sg_log.Info($"DevAlarmReport,来源方IP:{ip},接受数据:{receivedData}");

记录Error日志
sg_log.Error(Error);
Error 等级日志根据动态配置会存储在
在这里插入图片描述

using SuperSocket.SocketBase.Command;
using SuperSocket.SocketBase.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Newtonsoft.Json;
using System.Configuration;

using log4net;
using JMT.FrmXinYiChangCommunication.Common;
using JMT.FrmXinYiChangCommunication.Entity;

namespace JMT.FrmXinYiChangLightState
{
    //接收设备停机开机状态信息-过滤器
    public class DevAlarmReport : CommandBase<SocketSession, BinaryRequestInfo>
    {
        //SqlSugarClient Db;
        //List<Device> devices = new List<Device>();
        //public XYCBll bll = new XYCBll();
        //public XmlBuilders XmlBuilders = new XmlBuilders();
        Dictionary<string, string> response = new Dictionary<string, string>();
        //日志
        private static readonly log4net.ILog sg_log = LoggerManager.GetLogger(typeof(DevAlarmReport));
       // public SuperSocketService service = new SuperSocketService();

        public static string CmdName = "DevAlarmReport";
        int light;
        public override void ExecuteCommand(SocketSession session, BinaryRequestInfo requestInfo)
        {
            try
            {
                string ip = session.RemoteEndPoint.Address.ToString();
                string receivedData = Encoding.UTF8.GetString(requestInfo.Body);
                截取头部4个字节长度
                var startIndex = receivedData.IndexOf("{");
                var dataStartStr = receivedData.Substring(startIndex, receivedData.Length - startIndex);
                var endIndex = dataStartStr.IndexOf("}");
                // string str = receivedData.Substring(3, receivedData.Length - 4);
                var dataStr = dataStartStr.Substring(0, endIndex + 1);

                sg_log.Info($"DevAlarmReport,来源方IP:{ip},接受数据:{receivedData}");

                // 处理数据
                DevStatusRecord report = JsonConvert.DeserializeObject<DevStatusRecord>(dataStr);
                CheckRequestMsg(report);
                report.MessagePushTime = DateTime.Now;

                
                // 添加到队列

                // 响应机台
                string ResposeResult = JsonConvert.SerializeObject(CommandResponseResult.Success(CmdName));
                byte[] sendData = ResposeResult.ToByteByStr();
                session.Send(sendData, 0, sendData.Length);
            }
            catch(Exception ex)
            {
                string Error = $"命令:[{CmdName}],解析处理数据出现问题:{ex.Message}";
                string ResposeResult = JsonConvert.SerializeObject(CommandResponseResult.Error(CmdName, Error));
                byte[] sendData = ResposeResult.ToByteByStr();
                session.Send(sendData, 0, sendData.Length);
                sg_log.Error(Error);
            }
        }

        /// <summary>
        /// 校验请求参数
        /// </summary>
        /// <param name="record"></param>
        /// <exception cref="Exception"></exception>
        public void CheckRequestMsg(DevStatusRecord record)
        {
            if (string.IsNullOrEmpty(record.EqNo))
            {
                throw new Exception($"机台号不能为空!");
            }
        }
    }

}


不会影响原有的log4net.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<!-- This section contains the log4net configuration settings -->
	<log4net>
		<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
			<layout type="log4net.Layout.PatternLayout" value="%date [%thread] %-5level %logger - %message%newline" />
		</appender>

    <appender name="LogInfoAppender" type="log4net.Appender.RollingFileAppender">
			<file value="Logs/Info/" />
			<appendToFile value="true" />
			<!-- 文件的编码方式 -->
			<param name="Encoding" value="UTF-8"/>
			<rollingStyle value="Date" />
			<staticLogFileName value="false" />
			<datePattern value="yyyyMMdd_HH'.log'" />
			<maximumFileSize value="15MB" />
			<!--最小锁定模型以允许多个进程可以写入同一个文件-->
			<param name="lockingModel"  type="log4net.Appender.FileAppender+MinimalLock" />
			<layout type="log4net.Layout.PatternLayout">
				<param name="ConversionPattern" value="&#13;&#10;--------------------------------------------------------------------------------------&#13;&#10;%n日志时间:%d [%t] %n日志级别:%-5p %n日 志 类:%c [%x] %n%m" />
				<!--<param name="Header" value="&#13;&#10;======================================================================================&#13;&#10;" />-->
			</layout>
			<filter type="log4net.Filter.LevelRangeFilter">
				<param name="LevelMin" value="DEBUG"/>
				<param name="LevelMax" value="INFO"/>
			</filter>
			<param name="Encoding" value="utf-8" />
		</appender>

		<appender name="RollingErrorLogFileAppender" type="log4net.Appender.RollingFileAppender">
			<file value="Logs/Error/" />
			<appendToFile value="true" />
			<rollingStyle value="Date" />
			<staticLogFileName value="false" />
			<datePattern value="yyyyMMdd_HH'Err.log'" />
			<maximumFileSize value="15MB" />
			<!--最小锁定模型以允许多个进程可以写入同一个文件-->
			<param name="lockingModel"  type="log4net.Appender.FileAppender+MinimalLock" />
			<layout type="log4net.Layout.PatternLayout">
				<param name="ConversionPattern" value="%n&#13;&#10;--------------------------------------------------------------------------------------&#13;&#10;%n日志时间:%d [%t] %n日志级别:%-5p %n日 志 类:%c [%x] %n%m" />
			</layout>
			<filter type="log4net.Filter.LevelRangeFilter">
				<param name="LevelMin" value="WARN"/>
				<param name="LevelMax" value="FATAL"/>
			</filter>
			<param name="Encoding" value="utf-8" />
		</appender>

    <!-- 设备联机日志 -->
    <appender name="ConnectedClosed" type="log4net.Appender.RollingFileAppender">
			<file value="Logs/ConnectedClosed/" />
			<appendToFile value="true" />
			<!-- 文件的编码方式 -->
			<param name="Encoding" value="UTF-8"/>
			<rollingStyle value="Date" />
			<staticLogFileName value="false" />
			<datePattern value="yyyyMMdd'ConnectedClosed.log'" />
			<maximumFileSize value="15MB" />
			<!--最小锁定模型以允许多个进程可以写入同一个文件-->
			<param name="lockingModel"  type="log4net.Appender.FileAppender+MinimalLock" />
			<layout type="log4net.Layout.PatternLayout">
				<param name="ConversionPattern" value="&#13;&#10;--------------------------------------------------------------------------------------&#13;&#10;%n日志时间:%d [%t] %n日志级别:%-5p %n日 志 类:%c [%x] %n%m" />
				<!--<param name="Header" value="&#13;&#10;======================================================================================&#13;&#10;" />-->
			</layout>
			<filter type="log4net.Filter.LevelRangeFilter">
				<param name="LevelMin" value="DEBUG"/>
				<param name="LevelMax" value="INFO"/>
			</filter>
			<param name="Encoding" value="utf-8" />
		</appender>
    <!-- 发送命令接收命令日志 -->
    <appender name="SendGet" type="log4net.Appender.RollingFileAppender">
      <file value="Logs/SendGet/" />
      <appendToFile value="true" />
      <!-- 文件的编码方式 -->
      <param name="Encoding" value="UTF-8"/>
      <rollingStyle value="Date" />
      <staticLogFileName value="false" />
      <datePattern value="yyyyMMdd'SendGet.log'" />
      <maximumFileSize value="15MB" />
      <!--最小锁定模型以允许多个进程可以写入同一个文件-->
      <param name="lockingModel"  type="log4net.Appender.FileAppender+MinimalLock" />
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="&#13;&#10;--------------------------------------------------------------------------------------&#13;&#10;%n日志时间:%d [%t] %n日志级别:%-5p %n日 志 类:%c [%x] %n%m" />
        <!--<param name="Header" value="&#13;&#10;======================================================================================&#13;&#10;" />-->
      </layout>
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="DEBUG"/>
        <param name="LevelMax" value="INFO"/>
      </filter>
      <param name="Encoding" value="utf-8" />
    </appender>
    <!-- 数据上传日志 -->
    <appender name="DataMes" type="log4net.Appender.RollingFileAppender">
      <file value="Logs/DataMes/" />
      <appendToFile value="true" />
      <!-- 文件的编码方式 -->
      <param name="Encoding" value="UTF-8"/>
      <rollingStyle value="Date" />
      <staticLogFileName value="false" />
      <datePattern value="yyyyMMdd_HH'DataMes.log'" />
      <maximumFileSize value="15MB" />
      <!--最小锁定模型以允许多个进程可以写入同一个文件-->
      <param name="lockingModel"  type="log4net.Appender.FileAppender+MinimalLock" />
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="&#13;&#10;--------------------------------------------------------------------------------------&#13;&#10;%n日志时间:%d [%t] %n日志级别:%-5p %n日 志 类:%c [%x] %n%m" />
        <!--<param name="Header" value="&#13;&#10;======================================================================================&#13;&#10;" />-->
      </layout>
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="DEBUG"/>
        <param name="LevelMax" value="INFO"/>
      </filter>
      <param name="Encoding" value="utf-8" />
    </appender>




    <!-- Setup the root category, add the appenders and set the default level -->
		<!--<root>
			<level value="ALL" />
			<appender-ref ref="LogInfo" />
			<appender-ref ref="RollingErrorLogFileAppender" />
			<appender-ref ref="ResolveLogInfo" />
		</root>-->

		<logger name="LogInfo">
			<appender-ref ref="LogInfoAppender" />
		</logger>
    <logger name="ConnectedClosed">
      <appender-ref ref="ConnectedClosed" />
    </logger>
    <logger name="SendGet">
      <appender-ref ref="SendGet"/>
    </logger>
    <logger name="DataMes">
      <appender-ref ref="DataMes"/>
    </logger>
    

		<!--<logger name="ConsoleLog" additivity="true">
			--><!--additivity 是否确认子日志对象继承父日志对象的appender列表,默认为True:确认--><!--
			--><!--注意优先级,优先级低的要放在后面--><!--
			<level value="ALL" />
			<appender-ref ref="ConsoleAppender" />
		</logger>-->

	</log4net>
</configuration>

在这里插入图片描述
在这里插入图片描述

package com.university.demo.util.spark; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.sql.DataFrame; import org.apache.spark.sql.Row; import org.apache.spark.sql.SQLContext; import org.springframework.stereotype.Component; import java.util.Properties; @Component public class Sparkdao { private final String DB_URL = "jdbc:mysql://localhost:3396/kaoyan?useSSL=false&serverTimezone=Asia/Shanghai"; private final String DB_USERNAME = "root"; private final String DB_PASSWORD = "123456"; private SparkConf sparkConf; private JavaSparkContext javaSparkContext; private SQLContext sqlContext; public Properties getDBProperties(){ Properties props = new Properties(); props.put("user", DB_USERNAME); props.put("password", DB_PASSWORD); return props; } /* * 初始化Load * 创建sparkContext, sqlContext, hiveContext * */ public Sparkdao() { initSparckContext(); initSQLContext(); } /* * 创建sparkContext * */ private void initSparckContext() { String warehouseLocation = System.getProperty("user.dir"); sparkConf = new SparkConf() .setAppName("from-to-mysql") .set("spark.sql.warehouse.dir", warehouseLocation) .setMaster("local"); javaSparkContext = new JavaSparkContext(sparkConf); } /* * 创建sqlContext * 用于读写MySQL中的数据 * */ private void initSQLContext() { sqlContext = new SQLContext(javaSparkContext); } public Integer count(String table) { DataFrame rows = sqlContext.read().jdbc(DB_URL, table, getDBProperties()).where("1=1"); JavaRDD<Row> testRdd= rows.toJavaRDD(); return testRdd.collect().size(); } public Integer countLog(String type) { DataFrame rows = sqlContext.read().jdbc(DB_URL, "tb_log", getDBProperties()). where("opt ='" + type + "'"); JavaRDD<Row> testRdd= rows.toJavaRDD(); return testRdd.collect().size(); } }
最新发布
03-28
<think>嗯,我现在需要分析用户提供的这个Java类`Sparkdao`,它是关于使用Spark连接MySQL数据库进行数据操作的。首先,我应该仔细阅读代码,理解各个部分的作用,然后根据用户的提问,可能需要解释这个类的结构、功能以及可能的改进点。 首先,用户可能希望了解这个类的作用。看起来这是一个Spring组件,用于通过Spark SQL与MySQL交互,主要功能是统计数据库表中的记录数,以及按条件统计日志表中的记录数。需要注意代码中的依赖导入是否正确,比如Spark的相关库是否合适,以及MySQL的连接配置是否正确。 接下来,构造函数`Sparkdao()`调用了`initSparckContext()`和`initSQLContext()`,这里可能存在拼写错误,应该是`initSparkContext`,但代码中写成了`initSparckContext`,这可能是一个笔误。此外,初始化SparkConf时设置了本地模式,适用于测试,但在生产环境中可能需要调整。 在`initSparkContext`方法中,设置了应用名称和warehouse目录,这里使用`System.getProperty("user.dir")`来获取当前工作目录,可能没问题,但需要确认路径是否正确,是否有写入权限。另外,`setMaster("local")`指定本地运行,如果要在集群上运行,可能需要修改。 `initSQLContext`创建了SQLContext实例,用于执行SQL操作,但注意到代码中并没有使用HiveContext,所以如果有Hive的需求,可能需要调整。不过当前类名和用途是连接MySQL,所以可能不需要Hive支持。 `count`方法通过JDBC读取整个表的数据,然后转换为RDD并收集结果来计算大小。这种方法效率不高,因为会拉取所有数据到Driver端,尤其是当表很大时,会导致性能问题甚至内存溢出。更好的做法是使用聚合查询,比如`SELECT COUNT(*) FROM table`,直接在数据库端计算,减少数据传输。 同样,`countLog`方法拼接SQL条件,存在SQL注入的风险,应该使用参数化查询或者过滤条件的安全方法。比如,直接拼接字符串`"opt ='" + type + "'"`,如果`type`包含单引号或其他特殊字符,可能导致语法错误或注入攻击。应该使用Spark的Column条件或者参数化方式来处理。 另外,数据库连接的URL中有`kaoyan`数据库,端口是3396,而通常MySQL默认端口是3306,这里需要确认是否正确。还有,连接参数中`useSSL=false`可能在生产环境中不安全,建议启用SSL或者明确知道在开发环境中关闭是安全的。 关于Spring的集成,该类被标记为`@Component`,说明由Spring管理,但Spark的上下文生命周期需要仔细处理,避免多次实例化导致资源浪费。通常,SparkContext应该是一个单例,这里每次创建`Sparkdao`实例都会新建,可能需要调整为单例模式,或者通过Spring配置来管理。 代码中的`JavaSparkContext`和`SQLContext`是否正确关闭?目前没有看到`close`方法的调用,可能会导致资源泄漏。应该在不再需要时关闭这些上下文,比如添加一个销毁方法,使用`@PreDestroy`注解来关闭资源。 另外,使用`DataFrame`这个类名,在较新的Spark版本中已被`Dataset<Row>`取代,可能需要检查Spark版本兼容性。如果使用的是旧版本(如Spark 1.x),那么没问题,但如果是较新的版本,可能需要更新代码。 总结一下,这个类的主要功能是通过Spark SQL连接MySQL数据库,进行简单的行数统计,但存在效率问题、潜在的安全风险和资源管理问题。改进的方向包括优化统计方法、防止SQL注入、正确管理Spark资源,以及调整配置以适应生产环境。</think>以下是对`Sparkdao`类的分步解析及改进建议: --- ### **一、类功能概述** 1. **核心作用**:通过Apache Spark连接MySQL数据库,统计指定表的记录总数,或按类型统计日志表记录数。 2. **依赖组件**: - `SparkConf`:配置Spark应用(本地模式)。 - `JavaSparkContext`:Spark执行入口。 - `SQLContext`:执行SQL操作。 3. **集成Spring**:通过`@Component`注解由Spring管理生命周期。 --- ### **二、关键代码解析** #### **1. 初始化Spark上下文** ```java private void initSparckContext() { String warehouseLocation = System.getProperty("user.dir"); sparkConf = new SparkConf() .setAppName("from-to-mysql") .set("spark.sql.warehouse.dir", warehouseLocation) .setMaster("local"); javaSparkContext = new JavaSparkContext(sparkConf); } ``` - **潜在问题**: - 拼写错误:方法名`initSparckContext`应为`initSparkContext`。 - 本地模式限制:`setMaster("local")`仅适用于测试,生产需改为集群模式(如`yarn`)。 - 路径权限:`warehouseLocation`指向项目目录,需确保Spark有写入权限。 #### **2. 统计表记录数(低效实现)** ```java public Integer count(String table) { DataFrame rows = sqlContext.read().jdbc(DB_URL, table, getDBProperties()).where("1=1"); JavaRDD<Row> testRdd = rows.toJavaRDD(); return testRdd.collect().size(); } ``` - **问题分析**: - 全表加载:`where("1=1")`无实际过滤作用,会拉取全表数据到Driver端。 - 性能瓶颈:`collect()`将数据载入内存,大表易引发OOM错误。 #### **3. 条件统计日志记录(SQL注入风险)** ```java public Integer countLog(String type) { DataFrame rows = sqlContext.read().jdbc(DB_URL, "tb_log", getDBProperties()) .where("opt ='" + type + "'"); return rows.toJavaRDD().collect().size(); } ``` - **安全问题**: - 字符串拼接:若`type`包含单引号(如`' OR '1'='1`),会导致SQL注入。 - 建议使用参数化过滤条件(如`.filter(col("opt").equalTo(type))`)。 --- ### **三、改进建议** #### **1. 优化统计查询** - **直接使用SQL聚合**: ```java public Integer count(String table) { String query = "(SELECT COUNT(*) AS total FROM " + table + ") AS tmp"; DataFrame df = sqlContext.read().jdbc(DB_URL, query, getDBProperties()); return df.first().getLong(0).intValue(); } ``` - 优点:仅传输单个数字,避免全表拉取。 #### **2. 防止SQL注入** - **使用Spark过滤API**: ```java public Integer countLog(String type) { DataFrame df = sqlContext.read().jdbc(DB_URL, "tb_log", getDBProperties()); DataFrame filtered = df.filter(functions.col("opt").equalTo(type)); return (int) filtered.count(); } ``` - 安全机制:利用Spark的Column条件,避免拼接字符串。 #### **3. 资源管理优化** - **显式关闭资源**: ```java @PreDestroy public void close() { if (javaSparkContext != null) { javaSparkContext.close(); } } ``` - 防止内存泄漏:在Spring销毁Bean时关闭Spark上下文。 #### **4. 配置调整** - **生产环境配置**: - 移除`setMaster("local")`,通过提交脚本指定集群模式。 - 启用SSL:修改JDBC URL为`useSSL=true`(需配置证书)。 --- ### **四、其他注意事项** 1. **版本兼容性**: - `DataFrame`在Spark 2.0+中已更名为`Dataset<Row>`,建议检查Spark版本。 2. **连接池配置**: - 添加连接池(如HikariCP)管理数据库连接,避免频繁创建连接开销。 --- ### **五、总结** - **当前代码定位**:适用于小规模数据测试,但存在性能和安全风险。 - **改进方向**:优化查询逻辑、防范注入、完善资源管理,适配生产环境需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值