跳转至

Socket 日志接入示例


本篇主要介绍 Java Go Python 日志框架 如何配置 socket 输出到 datakit socket 日志采集器中。

文件采集和socket是互斥开启socket之前 请先关闭文件采集 请先配置好 logging.conf 具体配置说明

Java

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

虽然文件名有区别,但是log4j查找配置文件时,都是去classpath目录下查找,按照规范: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 配置日志传输到本机9540端口,protocol默认tcp -->
 <Socket name="socketname" host="localHost" port="9540" charset="utf8">
     <!-- 自定义 输出格式  序列布局-->
     <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>

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

     <!-- 注意: 配置 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格式化一起使用。

datakit 同时支持从文件中采集日志 从文本中采集日志 ,可作为socket采集不可用时的最佳方案。

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 上。