跳转至

Socket Logging

本篇主要介绍 Java/Go/Python 日志框架如何配置 Socket,将日志发送给 Datakit 日志采集器中。

文件采集和 Socket 采集是互斥的,开启 Socket 采集之前,请先关闭文件采集,参见日志采集配置

配置

Java

在配置 log4j 的时候需要注意,log4j v1,默认是使用 .properties 文件进行配置;而目前 log4j-v2 使用 XML 文件进行配置。

虽然文件名有区别,但是 log4j 查找配置文件时,都是去 Class Path 目录下查找,按照规范:v1 的配置在 resources/log4j.properties,v2 配置在 resources/log4j.xml

log4j(v2)

在 Maven 的配置中导入 log4j 2.x 的 jar 包:

<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-api</artifactId>
   <version>2.6.2</version>
</dependency>

<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-core</artifactId>
   <version>2.6.2</version>
</dependency>

resources 中配置 log4j.xml,添加 Socket Appender

 <!-- Socket appender socket 配置日志传输到本机 9530 端口,protocol 默认 TCP -->
 <Socket name="socketname" host="localhost" port="9530" charset="utf8">
     <!-- 自定义输出格式和序列布局 -->
     <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level [traceId=%X{trace_id} spanId=%X{span_id}] %class{36} %L %M - %msg%xEx%n"/>

     <!--注意:不要开启序列化传输到 socket 采集器上,目前 DataKit 无法反序列化,请使用纯文本形式传输-->
     <!-- <SerializedLayout/>-->

     <!-- 第二种输出格式 json-->
     <!-- 注意:配置 compact eventEol 一定要是 true  这样单条日志输出为一行-->
     <!-- 将日志发送到观测云上后会自动将 JSON 展开,所以在这里建议您将日志单条单行输出 -->
     <!-- <JsonLayout  properties="true" compact="true" complete="false" eventEol="true"/>-->
 </Socket>

 <!-- 然后定义 logger,只有定义了 logger 并引入的 appender,appender 才会生效 -->
 <loggers>
      <root level="trace">
          <appender-ref ref="Console"/>
          <appender-ref ref="socketname"/>
      </root>
 </loggers>

Java 代码示例:

package com.example;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.PrintWriter;
import java.io.StringWriter;

public class logdemo {
    public static void main(String[] args) throws InterruptedException {
        Logger logger = LogManager.getLogger(logdemo.class);
       for (int i = 0; i < 5; i++) {
            logger.debug("this is log msg to  datakt");
        }

        try {
            int i = 0;
            int a = 5 / i; // 除 0 异常
        } catch (Exception e) {
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            String exceptionAsString = sw.toString();
            logger.error(exceptionAsString);
        }
    }
}

log4j(v1)

在 maven 的配置中导入 log4j 1.x 的 jar 包

 <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>1.2.17</version>
 </dependency>

到 resources 目录下 创建 log4j.properties 文件

log4j.rootLogger=INFO,server
# ... 其他配置

log4j.appender.server=org.apache.log4j.net.SocketAppender
log4j.appender.server.Port=<dk socket port>
log4j.appender.server.RemoteHost=<dk socket ip>
log4j.appender.server.ReconnectionDelay=10000

# 可配置成 JSON 格式
# log4j.appender.server.layout=net.logstash.log4j.JSONEventLayout
...

Logback

Logback 中的 SocketAppender 无法将纯文本发送到 Socket 上

问题是 SocketAppender 发送序列化 Java 对象而不是纯文本。您可以使用 log4j 输入,但并不建议更换日志组件,而是重写一个将日志数据发送为纯文本的 Appender,并且您将其与 JSON 格式化一起使用。

替代方案: Logback Logstash 插件最佳实践

Golang

Zap

Golang 中最常用的是 Uber 的 Zap 开源日志框架,Zap 支持自定义 Output 注入。

自定义日志输出器并注入到 zap.core

type soceketOutput struct {
    conn net.Conn
}

func (s *soceketOutput) Write(b []byte) (int, error) {
    return s.conn.Write(b)
}

func zapcal() {
    conn, _ := net.DialTCP("tcp", nil, DK_LOG_PORT)
    socket := &soceketOutput{
        conn: conn,
    }

    w := zapcore.AddSync(socket)

    core := zapcore.NewCore(zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
        TimeKey:        "ts",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "caller",
        FunctionKey:    zapcore.OmitKey,
        MessageKey:     "msg",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     zapcore.EpochTimeEncoder,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
    }),
        w,
        zapcore.InfoLevel)

    l := zap.New(core, zap.AddCaller())

    l.Info("======= message =======")
}

Python

logging.handlers.SocketHandler

原生的 socketHandler 通过 socket 发送的是日志对象,并不是纯文本形式,所以需要自定义 Handler 并重写 socketHandler 中的 makePickle(slef,record) 方法。

代码仅供参考:

import logging
import logging.handlers

logger = logging.getLogger("") # 实例化 logging

#自定义 class 并重写 makePickle 方法
class PlainTextTcpHandler(logging.handlers.SocketHandler):
    """ Sends plain text log message over TCP channel """

    def makePickle(self, record):
     message = self.formatter.format(record) + "\r\n"
     return message.encode()


def logging_init():
    # 创建文件 handler
    fh = logging.FileHandler("test.log", encoding="utf-8")
    # 创建自定义 handler
    plain = PlainTextTcpHandler("10.200.14.226", 9540)

    # 设置 logger 日志等级
    logger.setLevel(logging.INFO)

    # 设置输出日志格式
    formatter = logging.Formatter(
        fmt="%(asctime)s - %(filename)s line:%(lineno)d - %(levelname)s: %(message)s"
    )

    # 为 handler 指定输出格式,注意大小写
    fh.setFormatter(formatter)
    plain.setFormatter(formatter)


    # 为 logger 添加的日志处理器
    logger.addHandler(fh)
    logger.addHandler(plain)

    return True

if __name__ == '__main__':
    logging_init()

    logger.debug(u"debug")
    logger.info(u"info")
    logger.warning(u"warning")
    logger.error(u"error")
    logger.critical(u"critical")

TODO: 后续会慢慢补充其他语言的日志框架去使用 socket 将日志发送到 DataKit 上。

文档评价

文档内容是否对您有帮助? ×