log4j写数据库存在单引号问题

项目中要求把日志信息写到文件的同时也把其写入数据库中, log4j.properties版本log4j-1.2.17, 配置如下

log4j.rootLogger=INFO,wjc,wjf,wjj
#common
log4j.appender.wjc.Encoding=GB2312
log4j.appender.wjc=org.apache.log4j.ConsoleAppender
log4j.appender.wjc.layout=org.apache.log4j.PatternLayout
log4j.appender.wjc.layout.ConversionPattern=%d %5p (%F:%L) - %m%n
log4j.appender.wjf.Encoding=GB2312
log4j.appender.wjf=org.apache.log4j.DailyRollingFileAppender
log4j.appender.wjf.Append=true
log4j.appender.wjf.File.DatePattern=‘.‘yyyy-MM-dd
log4j.appender.wjf.File=${catalina.base}/logs/jin.log
log4j.appender.wjf.layout=org.apache.log4j.PatternLayout
log4j.appender.wjf.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss } %t %p -%m%n
log4j.appender.wjj.Threshold=WARN
log4j.appender.wjj=com.common.component.syslog.Log4JdbcAppender
log4j.appender.wjj.layout=org.apache.log4j.PatternLayout
log4j.appender.wjj.sql=insert into cp_syslog(log_time, log_level, location, message) values (‘%d{yyyyMMddHHmmss }‘, ‘%-5p‘, ‘%C,%L‘, ‘%m‘)
#special
log4j.logger.org.apache.cxf=WARN

在代码中调用log.warn("abcd");等就可以把信息写入数据库了.错误日志:


log4j:ERROR Failed to excute sql
com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘classes/spring/spring‘‘)‘ at line 1
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:936)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2870)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1573)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1665)
at com.mysql.jdbc.Connection.execSQL(Connection.java:3170)
at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1316)
at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1235)
at com.joysim.common.component.syslog.Log4JdbcAppender.execute(Log4JdbcAppender.java:52)
at org.apache.log4j.jdbc.JDBCAppender.flushBuffer(JDBCAppender.java:289)
at org.apache.log4j.jdbc.JDBCAppender.append(JDBCAppender.java:186)
at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)

经调试发现要执行的sql语句里面包含了单引号,而我们知道要插入数据库中单引号是要进行转义处理的.

从错误信息中跟进发现sql是在JDBCAppender.flushBuffer()方法中进行了处理, 源码如下:

public void flushBuffer() {
    //Do the actual logging
    removes.ensureCapacity(buffer.size());
    for (Iterator i = buffer.iterator(); i.hasNext();) {
      LoggingEvent logEvent = (LoggingEvent)i.next();
      try {
        String sql = getLogStatement(logEvent);
        execute(sql);
      }
      catch (SQLException e) {
        errorHandler.error("Failed to excute sql", e,
               ErrorCode.FLUSH_FAILURE);
      } finally {
        removes.add(logEvent);
      }
    }

打印发现是getLogStatement(logEvent)对sql进行了处理, 该方法的代码如下:

  /**
   * By default getLogStatement sends the event to the required Layout object.
   * The layout will format the given pattern into a workable SQL string.
   *
   * Overriding this provides direct access to the LoggingEvent
   * when constructing the logging statement.
   *
   */
  protected String getLogStatement(LoggingEvent event) {
    return getLayout().format(event);
  }

搜索setLayout()方法, 发现是在setSql(String s)方法中进行了赋值, 该方法代码如下:

public void setSql(String s) {
    sqlStatement = s;
    if (getLayout() == null) {
        this.setLayout(new PatternLayout(s));
    }
    else {
        ((PatternLayout)getLayout()).setConversionPattern(s);
    }
  }

该方法的意思就是如果log4j配置文件没有为jdbcAppender配置patterLayout, 那么会默认指定一个PatternLayout对象给jdbcAppender. 接下来就看一下PatternLayout的format方法, 代码如下:

/**
     Produces a formatted string as specified by the conversion pattern.
  */
  public String format(LoggingEvent event) {
    // Reset working stringbuffer
    if(sbuf.capacity() > MAX_CAPACITY) {
      sbuf = new StringBuffer(BUF_SIZE);
    } else {
      sbuf.setLength(0);
    }

    PatternConverter c = head;

    while(c != null) {
      c.format(sbuf, event);
      c = c.next;
    }
    return sbuf.toString();
  }

head在其构造方法中进行了赋值, 代码如下:

  public PatternLayout(String pattern) {
    this.pattern = pattern;
    head = createPatternParser((pattern == null) ? DEFAULT_CONVERSION_PATTERN :
                 pattern).parse();
  }

或是在setConversionPattern方法中进行赋值

  public void setConversionPattern(String conversionPattern) {
    pattern = conversionPattern;
    head = createPatternParser(conversionPattern).parse();
  }

从上面的代码中可以看到在JDBCAppender的setSql方法中对上述两方法进行了调用.

执行parse(),

public org.apache.log4j.helpers.PatternConverter parse() {
    return new BridgePatternConverter(pattern);
  }

构造方法
 public BridgePatternConverter(
    final String pattern) {
    next = null;
    handlesExceptions = false;

    List converters = new ArrayList();
    List fields = new ArrayList();
    Map converterRegistry = null;

    PatternParser.parse(
      pattern, converters, fields, converterRegistry,
      PatternParser.getPatternLayoutRules());

    patternConverters = new LoggingEventPatternConverter[converters.size()];
    patternFields = new FormattingInfo[converters.size()];

    int i = 0;
    Iterator converterIter = converters.iterator();
    Iterator fieldIter = fields.iterator();

    while (converterIter.hasNext()) {
      Object converter = converterIter.next();

      if (converter instanceof LoggingEventPatternConverter) {
        patternConverters[i] = (LoggingEventPatternConverter) converter;
        handlesExceptions |= patternConverters[i].handlesThrowable();
      } else {
        patternConverters[i] =
          new org.apache.log4j.pattern.LiteralPatternConverter("");
      }

      if (fieldIter.hasNext()) {
        patternFields[i] = (FormattingInfo) fieldIter.next();
      } else {
        patternFields[i] = FormattingInfo.getDefault();
      }

      i++;
    }
  }

解析
public static void parse(
    final String pattern, final List patternConverters,
    final List formattingInfos, final Map converterRegistry, final Map rules) {
    if (pattern == null) {
      throw new NullPointerException("pattern");
    }

    StringBuffer currentLiteral = new StringBuffer(32);

    int patternLength = pattern.length();
    int state = LITERAL_STATE;
    char c;
    int i = 0;
    FormattingInfo formattingInfo = FormattingInfo.getDefault();

    while (i < patternLength) {
      c = pattern.charAt(i++);

      switch (state) {
      case LITERAL_STATE:

        // In literal state, the last char is always a literal.
        if (i == patternLength) {
          currentLiteral.append(c);

          continue;
        }

        if (c == ESCAPE_CHAR) {
          // peek at the next char.
          switch (pattern.charAt(i)) {
          case ESCAPE_CHAR:
            currentLiteral.append(c);
            i++; // move pointer

            break;

          default:

            if (currentLiteral.length() != 0) {
              patternConverters.add(
                new LiteralPatternConverter(currentLiteral.toString()));
              formattingInfos.add(FormattingInfo.getDefault());
            }

            currentLiteral.setLength(0);
            currentLiteral.append(c); // append %
            state = CONVERTER_STATE;
            formattingInfo = FormattingInfo.getDefault();
          }
        } else {
          currentLiteral.append(c);
        }

        break;

      case CONVERTER_STATE:
        currentLiteral.append(c);

        switch (c) {
        case ‘-‘:
          formattingInfo =
            new FormattingInfo(
              true, formattingInfo.getMinLength(),
              formattingInfo.getMaxLength());

          break;

        case ‘.‘:
          state = DOT_STATE;

          break;

        default:

          if ((c >= ‘0‘) && (c <= ‘9‘)) {
            formattingInfo =
              new FormattingInfo(
                formattingInfo.isLeftAligned(), c - ‘0‘,
                formattingInfo.getMaxLength());
            state = MIN_STATE;
          } else {
            i = finalizeConverter(
                c, pattern, i, currentLiteral, formattingInfo,
                converterRegistry, rules, patternConverters, formattingInfos);

            // Next pattern is assumed to be a literal.
            state = LITERAL_STATE;
            formattingInfo = FormattingInfo.getDefault();
            currentLiteral.setLength(0);
          }
        } // switch

        break;

      case MIN_STATE:
        currentLiteral.append(c);

        if ((c >= ‘0‘) && (c <= ‘9‘)) {
          formattingInfo =
            new FormattingInfo(
              formattingInfo.isLeftAligned(),
              (formattingInfo.getMinLength() * 10) + (c - ‘0‘),
              formattingInfo.getMaxLength());
        } else if (c == ‘.‘) {
          state = DOT_STATE;
        } else {
          i = finalizeConverter(
              c, pattern, i, currentLiteral, formattingInfo,
              converterRegistry, rules, patternConverters, formattingInfos);
          state = LITERAL_STATE;
          formattingInfo = FormattingInfo.getDefault();
          currentLiteral.setLength(0);
        }

        break;

      case DOT_STATE:
        currentLiteral.append(c);

        if ((c >= ‘0‘) && (c <= ‘9‘)) {
          formattingInfo =
            new FormattingInfo(
              formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
              c - ‘0‘);
          state = MAX_STATE;
        } else {
            LogLog.error(
              "Error occured in position " + i
              + ".\n Was expecting digit, instead got char \"" + c + "\".");

          state = LITERAL_STATE;
        }

        break;

      case MAX_STATE:
        currentLiteral.append(c);

        if ((c >= ‘0‘) && (c <= ‘9‘)) {
          formattingInfo =
            new FormattingInfo(
              formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
              (formattingInfo.getMaxLength() * 10) + (c - ‘0‘));
        } else {
          i = finalizeConverter(
              c, pattern, i, currentLiteral, formattingInfo,
              converterRegistry, rules, patternConverters, formattingInfos);
          state = LITERAL_STATE;
          formattingInfo = FormattingInfo.getDefault();
          currentLiteral.setLength(0);
        }

        break;
      } // switch
    }

    // while
    if (currentLiteral.length() != 0) {
      patternConverters.add(
        new LiteralPatternConverter(currentLiteral.toString()));
      formattingInfos.add(FormattingInfo.getDefault());
    }
  }

代码较长, 大概的意思就是把log4j配置文件中的sql语句, insert into cp_syslog(log_time, log_level, location, message) values (‘%d{yyyyMMddHHmmss }‘, ‘%-5p‘, ‘%C,%L‘, ‘%m‘), 中的p, c, l, m等都转成PatternConverter, 并返回第一个PatternConverter, 说到底就是一个链表, 而this.head是这个链表的头元素. 现在主要就是关注各个PatternConverter的format方法,

static {
    // We set the global rules in the static initializer of PatternParser class
    Map rules = new HashMap(17);
    rules.put("c", LoggerPatternConverter.class);
    rules.put("logger", LoggerPatternConverter.class);

    rules.put("C", ClassNamePatternConverter.class);
    rules.put("class", ClassNamePatternConverter.class);

    rules.put("d", DatePatternConverter.class);
    rules.put("date", DatePatternConverter.class);

    rules.put("F", FileLocationPatternConverter.class);
    rules.put("file", FileLocationPatternConverter.class);

    rules.put("l", FullLocationPatternConverter.class);

    rules.put("L", LineLocationPatternConverter.class);
    rules.put("line", LineLocationPatternConverter.class);

    rules.put("m", MessagePatternConverter.class);
    rules.put("message", MessagePatternConverter.class);

    rules.put("n", LineSeparatorPatternConverter.class);

    rules.put("M", MethodLocationPatternConverter.class);
    rules.put("method", MethodLocationPatternConverter.class);

    rules.put("p", LevelPatternConverter.class);
    rules.put("level", LevelPatternConverter.class);

    rules.put("r", RelativeTimePatternConverter.class);
    rules.put("relative", RelativeTimePatternConverter.class);

    rules.put("t", ThreadPatternConverter.class);
    rules.put("thread", ThreadPatternConverter.class);

    rules.put("x", NDCPatternConverter.class);
    rules.put("ndc", NDCPatternConverter.class);

    rules.put("X", PropertiesPatternConverter.class);
    rules.put("properties", PropertiesPatternConverter.class);

    rules.put("sn", SequenceNumberPatternConverter.class);
    rules.put("sequenceNumber", SequenceNumberPatternConverter.class);

    rules.put("throwable", ThrowableInformationPatternConverter.class);
    PATTERN_LAYOUT_RULES = new ReadOnlyMap(rules);

    Map fnameRules = new HashMap(4);
    fnameRules.put("d", FileDatePatternConverter.class);
    fnameRules.put("date", FileDatePatternConverter.class);
    fnameRules.put("i", IntegerPatternConverter.class);
    fnameRules.put("index", IntegerPatternConverter.class);

    FILENAME_PATTERN_RULES = new ReadOnlyMap(fnameRules);
  }

MessagePatternConverter的format方法如下:

  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
    toAppendTo.append(event.getRenderedMessage());
  }

MyLoggingEvent类中有getThreadName和getRenderedMessage两个方法

很奇怪, 跟踪到这里发现源码中是有对单引号进行处理的, 可是为什么还会出现问题呢

ThreadPatternConverter 的format方法如下:

  public void format(final LoggingEvent event, final StringBuffer toAppendTo) {
    toAppendTo.append(event.getThreadName());
  }

我们可以在event.getThreadName()和event.getRenderedMessage()方法返回前对单引号进行替换, 因此我们可以写一个类扩展LoggingEvent, 重写LoggingEvent的getRenderedMessage和getThreadName方法, 代码如下:

public class MyLoggingEvent extends LoggingEvent {
    /** */
    private static final long serialVersionUID = -3499094864944744184L;

    public MyLoggingEvent(String fqnOfCategoryClass, Category logger, Priority level, Object message, Throwable throwable) {
        super(fqnOfCategoryClass, logger, level, message, throwable);
    }

    public String getThreadName() {
        String thrdName = super.getThreadName();
        if (thrdName.indexOf("‘") != -1) {
            thrdName = thrdName.replaceAll("‘", "‘‘");
        }
        return thrdName;
    }

    public String getRenderedMessage() {
        String msg = super.getRenderedMessage();
        if (msg.indexOf("‘") != -1) {
            msg = msg.replaceAll("‘", "‘‘");
        }
        return msg;
    }
}

最后在我们log4j配置文件中指定的Log4JdbcAppender处理类中, 覆写JDBCAppender的getLogStatement方法, 让程序执行我们重写的MyLoggingEvent类中的相应方法, 代码如下:

public class Log4JdbcAppender extends JDBCAppender {

    private JdbcSupport jdbcSupport;

    @Override
    protected void closeConnection(Connection conn) {
        try {
            if (conn != null && !conn.isClosed())
                conn.close();
        } catch (SQLException e) {
            errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
        }
    }

    @Override
    protected Connection getConnection() throws SQLException {
        if (jdbcSupport == null) {
            jdbcSupport = SpringUtils.getBean(JdbcSupport.class, "jdbcSupport");
        }
        return jdbcSupport == null ? null : jdbcSupport.getJt().getDataSource().getConnection();
    }

    @Override
    protected void execute(String sql) throws SQLException {
        Connection conn = getConnection();
        if (conn != null) {
            Statement stmt = null;
            try {
                stmt = conn.createStatement();
                stmt.executeUpdate(sql);
            } catch (SQLException e) {
                if (stmt != null)
                    stmt.close();
                throw e;
            }
            stmt.close();
            closeConnection(conn);
        }
    }

    @Override
    protected String getLogStatement(LoggingEvent event) {
        String fqnOfCategoryClass=event.fqnOfCategoryClass;
        Category logger=event.getLogger();
        Priority level=event.getLevel();
        Object message=event.getMessage();
        Throwable throwable=null;
        MyLoggingEvent bEvent=new MyLoggingEvent(fqnOfCategoryClass,logger,level,message,throwable);
        return super.getLogStatement(bEvent);
    }
}

大功告成, 日志成功插入到数据库

log4j写数据库存在单引号问题

时间: 2024-10-29 04:07:41

log4j写数据库存在单引号问题的相关文章

SQL Server 重新初始化系统数据库中的单引号问题

在最近的数据库跨机房迁移中,由于硬件的限制,需要滚动式地将数据库一台台迁移到新机房,先在新机房搭建一个新环境,将数据迁移过去,再将旧机房的机器下架搬到新机房,重新配置后用于下一轮的升级,重新配置过程中,有以下几个问题: 1:由于机房和IP已经发生变化,因此需要按照命名规则修改服务器名称. 2:原数据库上的数据如登录账号/作业/链接服务器等等需要删除 3:部分服务器因磁盘变动,仅保留系统盘,部分SQL Server文件(如果系统数据库文件)已经丢失 对于问题1和问题2,可以进行手动删除或者写个脚本

数据库笔记:字符串单引号转义新方法

从Oracle 10g R1开始,数据库允许用另一个引用符号来代替单引号.当字符串中包含大量单引号时(如 I'm in Xi'an). 以前的表达方式如下: SQL>SELECT * FROM T WHERE ADDR='I''m in Xi''an'; 新表达方法如下: SQL>SELECT * FROM T WHERE ADDR=q'( I'm in Xi'an)';

原来数据库里的单引号是这么加进去的

使 SQL Server 遵从关于引号分隔标识符和文字字符串的 ISO 规则.由双引号分隔的标识符可以是 Transact-SQL 保留关键字,也可以包含 Transact-SQL 标识符语法约定通常不允许的字符. Transact-SQL 语法约定 语法   SET QUOTED_IDENTIFIER { ON | OFF } 注释 当 SET QUOTED_IDENTIFIER 为 ON 时,标识符可以由双引号分隔,而文字必须由单引号分隔.当 SET QUOTED_IDENTIFIER 为

php 单引号 双引号 ,php字符串/ hmtl / 数据库显示/ 及php的几个转化函数

* 以单引号为定界符的php字符串,支持两个转义\'和\\* 以双引号为定界符的php字符串,支持下列转义(\'会直接输出\' ,也会转义 \\):    \n 换行(LF 或 ASCII 字符 0x0A(10))     \r 回车(CR 或 ASCII 字符 0x0D(13))     \t 水平制表符(HT 或 ASCII 字符 0x09(9))     \\ 反斜线     \$ 美元符号     \" 双引号     \[0-7]{1,3}               此正则表达式序列

mysql单引号和双引号

表名,列名最好用`(esc下面那个,不用`会出错) 这就要从双引号和单引号的作用讲起:双引号里面的字段会经过编译器解释然后再当作HTML代码输出,但是单引号里面的不需要解释,直接输出.例如:$abc='I love u';echo $abc //结果是:I love uecho '$abc' //结果是:$abcecho "$abc" //结果是:I love u所以在对数据库里面的SQL语句赋值的时候也要用在双引号里面SQL="select a,b,c from ...&q

Mysql中where条件一个单引号引发的性能损耗

日常写SQL中可能会有一些小细节忽略了导致整个sql的性能下降了好几倍甚至几十倍,几百倍.以下这个示例就是mysql语句中的一个单引号('')引发的性能耗损,我相信很多朋友都遇到过,甚至还在这样写. 先看下我的表结构: CREATE TABLE `d_sku` ( `id` varchar(36) NOT NULL, `commodity_id` varchar(36) DEFAULT NULL, `counts` int(11) DEFAULT NULL, `price` double(15,

SQL语句中有关单引号、双引号和加号的问题

字符串数据是用单引号包在外面的,而+号只是用来连接这些字符串的. 数据库里的字段是整型的时候不要加单引号,是字符串的时候要加,其它类型根据实际情况来,双引号就是用来拼接字符串的,单引号是sql文的固有写法,因为你要动态的来拼接,涉及到变量,所以要用"+"来组合各个字符串片段.最终结果无非就是得出能在数据库查询分析器中执行的sql文. String sql = "insert into student values ( " + student.getId() + &q

JavaScript基础 输出含有双引号/单引号的字符串

镇场诗: 清心感悟智慧语,不着世间名与利.学水处下纳百川,舍尽贡高我慢意. 学有小成返哺根,愿铸一良心博客.诚心于此写经验,愿见文者得启发.------------------------------------------ code: 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=ut

ASP,VB,JAVASCRIPT 拼HTML时多层单引号双引号嵌套用法,实用(转载)

s.html中的单引号.双引号及其转义使用(转) 收藏   在js中对相关字符做判断或取值,或者拼HTML赋值的时候很多情况下都会用到这些,也是我刚刚遇到的问题,通过参考下面的这篇文章,一切都解决了,摘抄下来做个笔记!呵呵... ------ 在一个网页中的按钮,写onclick事件的处理代码,不小心写成如下:<input value="Test" type="button" onclick="alert(""OK"&q