006-Cpp webServer示例

Cpp webServer示例

1: 使用 c++11 标准库写一个 httpServer

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <thread>
#include <mutex>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define Server_Port  18080  // 服务listen端口

// 定义 HTTP 请求方法
enum class HttpMethod { GET, POST };

// HTTP 请求结构体
struct HttpRequest {
    HttpMethod method;
    std::string url;
    std::map<std::string, std::string> headers;
    std::string body;
};

// HTTP 响应结构体
struct HttpResponse {
    std::string status;
    std::map<std::string, std::string> headers;
    std::string body;
};

// 处理 HTTP 请求
// void handleRequest(const HttpRequest& req, int clientSocket) {
void handleRequest(int clientSocket) {
    HttpResponse res;
    // 构建 HTTP 响应
    res.status = "HTTP/1.1 200 OK";
    res.headers["Content-Type"] = "text/html";
    res.body = "<html><body><h1>Hello, World!</h1></body></html>";

    // 发送 HTTP 响应
    std::ostringstream oss;
    oss << res.status << "\r\n";
    for (const auto& header : res.headers) {
        oss << header.first << ": " << header.second << "\r\n";
    }
    oss << "Content-Length: " << res.body.length() << "\r\n";
    oss << "\r\n";
    oss << res.body;
    std::string response = oss.str();
    ssize_t n=write(clientSocket, response.c_str(), response.length()); // write用于将数据写入fd,返回值是成功写入的byte数
    if(n<0){
        std::cerr<<"Error writing response"<<std::endl;
    }else{
        std::cout<<"Bytes written :"<<n<<std::endl;
    }
    close(clientSocket);// 关闭客户端连接
}

// 启动 Web 服务器
void startWebServer() {
    int serverSocket, clientSocket;
    struct sockaddr_in serverAddr, clientAddr;
    socklen_t addrLen = sizeof(clientAddr);

    // 创建套接字
    serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == -1) {
        std::cerr << "Error creating socket" << std::endl;
        exit(1);
    }

    // 绑定地址和端口
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(Server_Port); // 监听端口
    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        std::cerr << "Error binding" << std::endl;
        close(serverSocket);
        exit(1);
    }

    // 监听连接
    if (listen(serverSocket, 10) == -1) {
        std::cerr << "Error listening" << std::endl;
        close(serverSocket);
        exit(1);
    }

    std::cout << "Web server started at http://localhost:"<<Server_Port << std::endl;

    while (true) {
        // 接受客户端连接
        clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &addrLen);
        if (clientSocket == -1) {
            std::cerr << "Error accepting connection" << std::endl;
            continue;
        }

        // 读取 HTTP 请求
        char buffer[1024];
        int bytesRead = read(clientSocket, buffer, sizeof(buffer));
        if (bytesRead <=0){
            std::cerr << "Error reading request" << std::endl;
            close(clientSocket);
            continue;
        }
        // 解析 HTTP 请求
        std::string requestString(buffer, bytesRead);
        std::istringstream iss(requestString);
        std::string line;
        HttpRequest req;
        if (std::getline(iss, line)) {
            std::istringstream issLine(line);
            std::string methodStr, url;
            issLine >> methodStr >> url;
            if (methodStr == "GET") {
                req.method = HttpMethod::GET;
            } else if (methodStr == "POST") {
                req.method = HttpMethod::POST;
            }
            req.url = url;
        }

        while (std::getline(iss, line)) {
            size_t colonPos = line.find(':');
            if (colonPos != std::string::npos) {
                std::string key = line.substr(0, colonPos);
                std::string value = line.substr(colonPos + 1);
                req.headers[key] = value;
            }
        }
        handleRequest(clientSocket); // 处理 HTTP 请求
    }
    // 关闭服务器套接字
    close(serverSocket);
}

int main() {
    startWebServer();
    return 0;
}

005-Cpp 动态内存分配函数

Cpp 动态内存分配函数

1: cpp 动态内存分配

  • void* malloc(unsigned int size);
  • void* realloc(void *ptr, unsigned int newsize);
  • void* calloc(size_t numElements, size_t sizeOfElement);
  • 加上一种new关键字,这四个都可以用来向内存申请空间

2: malloc

  • malloc(unsigned int size),它可以在内存的堆(Heap)区域申请连续大小为 size 个字节的空间,然后成功的话返回 void*指针(C/C++中,void*可以被强制转换为其他类型的指针,像 int*等等),这个指针是申请这段连续空间的首地址,如果申请失败的话就返回 NULL 指针。

3: calloc

  • calloc(unsigned int n, unsigned int size),它可以在内存的堆(Heap)区域申请连续 n 个空间,每个空间包含了 size 个字节的大小,也就是总共向内存申请了 n*size 个字节的空间大小,然后成功的话返回 void*指针(这列的也是可以被强制转换的),这个指针是申请这段连续空间的首地址,如果申请失败的话就返回 NULL 指针。

4: realloc

  • realloc(void *ptr, unsigned int newsize),这个的作用是向内存申请一个 newsize 的空间大小。当使用 malloc 申请的内存不够用的时候,就是用 realloc 来进行申请更大的空间,前面的指针 ptr 就是一开始用 malloc 申请所返回的指针。realloc 申请的方式是从内存堆的开始地址高地址查找一块区域,这块区域大于 newsize 的大小,申请成功后将原本的数据从开始到结束一起拷贝到新分配的内存区域,然后将原本 malloc 申请的区域释放掉(注意的是:原来的指针是自动释放的,不需要使用 free 来进行释放)。最后再这块区域的首地址返回。当内存不再使用时,要记得用 free()函数进行释放。

5: new

  • new是 C++提供的关键字,也是操作符,我们可以使用 new 在内存的堆上创建一个对象,在创建对象的时候,new 其实做了以下的操作:在堆上获得一块内存空间->调用构造函数(创建建档的类型变量时除外)->返回正确的指针;
  • char* p = new char(‘e’):分配1个char类型大小的内存空间;
  • char* p = new char[5]: 分配5个char类型大小的内存空间

6: demo.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <memory>
#include <cstring>
using namespace std;

/*
C++ 动态内存分配函数

- malloc 是 C/C++ 标准库中的函数,用于在堆上分配指定大小的内存空间,并返回指向该内存空间的指针。
在使用 malloc 分配内存后,需要手动调用 free 函数来释放这块内存空间,以防止内存泄漏。

- calloc 与 malloc 类似,也是用于在堆上分配内存空间的函数,但它会将分配到的内存空间初始化为零。
calloc 接受两个参数,分别是要分配的内存块的数量和每个内存块的大小。
与 malloc 一样,使用 calloc 分配的内存空间也需要通过调用 free 函数来释放。

- realloc 是用于重新分配内存空间大小的函数。它接受两个参数,一个是已经分配的内存空间的指针,
另一个是需要重新分配的内存空间的大小。
realloc 可以用于扩大或缩小已经分配的内存空间,如果新分配的内存空间大小大于原来的内存空间大小,
那么新分配的内存空间中未初始化的部分将保持未定义的值。与 malloc 和 calloc 一样,使用 realloc 分配的内存空间也需要通过调用 free 函数来释放

- new是C++提供的关键字,也是操作符,我们可以使用new在内存的堆上创建一个对象,在创建对象的时候,
new其实做了以下的操作:在堆上获得一块内存空间->调用构造函数(创建建档的类型变量时除外)->返回正确的指针

 */

//
void Malloc_Test(){
  char* ptr = (char*)malloc(20);                  // 向内存堆申请10个char类型的连续空间,并且返回首地址
  cout<<"malloc 指针变量的地址   :"<<&ptr<<endl;
  cout<<"malloc 申请到的空间首地址   :"<<static_cast<void*>(ptr)<<endl;
  strcpy(ptr,"malloc...lucas");                          // 如果你复制strcpy(ptr,"Servenssssssssssssssssssss");c程序就会中断,因为你申请的区域不够放了
  cout<<"malloc 申请到的空间赋值的地址:"<<static_cast<void*>(ptr)<<endl;
  free(ptr);
};

//
void Calloc_Test(){
  char* ptr=(char*)calloc(20,sizeof(char));           // 通过calloc申请10个char类型大小的内存空间
  cout<<"calloc 指针变量的地址   :"<<&ptr<<endl;
  cout<<"calloc 申请到的空间首地址时  :"<<static_cast<void*>(ptr)<<endl;
  strcpy(ptr,"calloc...lucas");
  cout<<"calloc 申请到的空间赋值的地址:"<<static_cast<void*>(ptr)<<endl;
//   cout<<"malloc 申请到的空间赋值的字符串:"<<strlen(ptr)<<endl;
  free(ptr);
};

void Realloc_Test(){
  char *ptr = (char*)malloc(6 * sizeof(char)); // 分配足够的内存来容纳 5 个字符和一个空终止符
  strcpy(ptr,"Serven"); // 将 6 个字符(包括空终止符)复制到分配的内存中
  cout<<"malloc 指针变量的地址   :"<<&ptr<<endl;
  cout<<"malloc 申请到的空间首地址时  :"<<static_cast<void*>(ptr)<<endl;

  char *ptru = (char*)realloc(ptr, 15 * sizeof(char)); // 分配足够的内存来容纳 14 个字符和一个空终止符
  strncpy(ptru,"Hello, Serven", 14); // 最多复制 14 个字符(包括空终止符)到分配的内存中
  ptru[14] = '\0'; // 手动添加空终止符以确保字符串正确终止
  cout<<"realloc 申请到的空间首地址时:"<<static_cast<void*>(ptru)<<endl;
  cout<<"realloc 申请到的空间赋值的字符串:"<<ptru<<endl;

  //free(ptr); // 无需释放 ptr,因为 realloc 会处理
  free(ptru);
}

int main(){
    cout<<"C++ 动态内存分配函数: malloc,calloc,realloc,new "<<endl;
    Malloc_Test();
    Calloc_Test();
    Realloc_Test();
    return 0;
}

7: 总结与比较

函数申请区域申请长度是否初始化头文件
mallocheapsizevoid*<stdlib.h>/<malloc.h>
callocheapn * sizevoid*<stdlib.h>/<malloc.h>
reallocheapnewsizevoid*
  • 如果申请调用成功,malloc()和 calloc()函数都会返回所分配的内存空间的首地址;
  • free()释放函数时紧跟着 malloc、calloc、realloc,因为它们都是返回 void*;而 new 使用的时 delete;
  • malloc 和 calloc 之间的区别就是:malloc 申请成功后没有初始化,这样会导致一些垃圾数据的存在,calloc 会初始化,将申请到的这一段连续空间初始化为 0,如果是实型数据的话,则会初始化为浮点数的 0;
  • malloc、calloc、realloc 三者都是返回 void*,C/C++支持强制转换 void*成其他类型的指针;
  • malloc 是在动态存储区域申请分配一块长度为 size 字节的连续区域,并返回这块区域的首地址;calloc 是在动态存储区域申请分配 n 块长度为 size 字节的区域,并返回这块区域的首地址;realloc 是将 ptr 内存大小增大到 newsize 个字节;
  • realloc 是从堆上分配内存,在扩大空间的时候会直接在现存的数据后面获得附加的字节,如果空间能够满足的话就扩展,如果不满足的话,那就从堆的开始地址开始寻找,找到第一个足够大的自由块,然后将现有的数据拷贝到新的位置,而老的那一块还是放到堆上。

01-云原生全景图

云原生全景图

1: 什么是云原生

2: 云原生发展

3: 云原生组成

3.1 不可变基础设施

3.2 微服务和服务网格

3.3 容器技术

3.4 DevOps

  • DevOps 是 Development 和 Operations 的组合词。它是一组过程、方法与系统的统称,用于促进开发(应用程序 / 软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。 DevOps全景图
  • CICD 持续集成 & 持续部署;

3.4.1 代码托管工具

  • Gitlab
  • Gitee
  • Github
  • 华为 CodeHub,阿里 Codeup,腾讯 Coding (企业级,收费)

3.4.2 集成流水线工具

  • 集成流水线就像传统的工业流水线一样,在经历构建、测试、交付之后,生产出一代一代更新迭代的软件版本。实现了软件产品小步迭代、高频发布、适时集成、稳定的系统演进线路图。在选择集成流水线工具的时候,我们需要关注:
    • 版本控制工具的支持;
    • 每个构建是否可以支持指定多个代码源 URLS;
    • 是否支持构建产物管理库,如公有云对象存储等;
    • 是否支持部署流水线,类似于一个或多个构建完成后触发另一个构建;
    • 是否支持并行构建;
    • 是否支持构建网格,以及对网格内机器管理的能力。如能否将多个构建同时分配到多个构建机器上执行,以提高执行速度;
    • 是否有良好的开放 API,比如触发构建 API、结果查询 API、标准的 Report 接口等;
    • 账户体系,是否支持第三方账户接入,如企业 LDAP 等;
    • 是否有良好的 Dashboard;
    • 多语言支持;
    • 与构建工具(如 Maven,Make,Rake,Nant、Node 等)和测试工具的集成

常用集成流水线工具

004-Cpp thread多线程用法整理

Cpp thread用法整理

1: Cpp thread 多线程

  • C++11 中加入了<thread>头文件,此头文件主要声明了std::thread线程类。C++11 的标准类std::thread对线程进行了封装,定义了 C++11 标准中的一些表示线程的类、用于互斥访问的类与方法等。

2: std::thread 类成员函数

  • get_id: 获取线程 ID,返回一个类型为 std:🧵:id 的对象;

03-C/Cpp常用头文件与库

C/Cpp常用头文件与库

1: C 头文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
  include <assert.h>   //设定插入点
  include <ctype.h>    //字符处理
  include <errno.h>    //定义错误码
  include <float.h>    //浮点数处理
  include <fstream.h>  //文件输入/输出
  include <iomanip.h>  //参数化输入/输出
  include <iostream.h> //数据流输入/输出
  include <limits.h>   //定义各种数据类型最值常量
  include <locale.h>   //定义本地化函数
  include <math.h>     //定义数学函数
  include <stdio.h>    //定义输入/输出函数
  include <stdlib.h>   //定义杂项函数及内存分配函数
  #include <string.h>  //字符串处理
  include <strstrea.h> //基于数组的输入/输出
  include <time.h>     //定义关于时间的函数
  #include <wchar.h>   //宽字符处理及输入/输出
  include <wctype.h>   //宽字符分类//

2:Cpp 头文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
include <algorithm>  //STL 通用算法
include <bitset>     //STL 位集容器
include <cctype>     //定义错误码
#include <cerrno>    //定义错误码
include <clocale>    //定义本地化函数
include <cmath>      //定义数学函数
include <complex>    //复数类
#include <cstdio>    /定义输入/输出函数
#include <cstdlib>   //定义杂项函数及内存分配函数
include <cstring>    //字符串处理
#include <ctime>     //定义关于时间的函数

include <deque>      //STL 双端队列容器

include <exception>  //异常处理类
include <fstream>    //文件输入/输出

include <functional> //STL 定义运算函数(代替运算符)

include <limits>     //定义各种数据类型最值常量

#include <list>      //STL 线性列表容器
#include <map>       //STL 映射容器
include <iomanip>
#include <ios>       //基本输入/输出支持
#include <iosfwd>    //输入/输出系统使用的前置声明
include <iostream>
include <istream>    //基本输入流
include <ostream>    //基本输出流
include <queue>      //STL 队列容器
include <set>        //STL 集合容器
#include <sstream>   //基于字符串的流
include <stack>      //STL 堆栈容器
include <stdexcept>  //标准异常类
include <streambuf>  //底层输入/输出支持
include <string>     //字符串类
include <utility>    //STL 通用模板类
include <vector>     //STL 动态数组容器
include <cwchar>
include <cwctype>

using namespace std;
///C99 增加
include <complex.h>  //复数处理
include <fenv.h>     //浮点环境
include <inttypes.h> //整数格式转换
include <stdbool.h>  //布尔环境
include <stdint.h>   //整型环境
include <tgmath.h>   //通用类型数学宏

3: STL 标准模板库

  • STL,英文全称 standard template library,中文可译为标准模板库或者泛型库,其包含有大量的模板类和模板函数,是 C++ 提供的一个基础模板的集合,用于完成诸如输入/输出、数学计算等功能;
  • STL 是一些容器、算法和其他一些组件的集合;

3.1 STL 的组成

STL 的组成含义其他
容器一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等-
算法STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 中,少部分位于头文件 -
迭代器在 C++ STL 中,对容器中数据的读和写,是通过迭代器完成的-
函数对象如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)-
适配器可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器-
内存分配器为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用-

3.2 STL 头文件

  • 按照 C++ 标准库的规定,所有标准头文件都不再有扩展名。以 为例,此为无扩展名的形式,而 <vector.h> 为有扩展名的形式。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
include <iterator>
include <functional>
include <vector>
include <list>
include <queue>
include <deque>
include <stack>
include <set>
include <map>
include <algorithm>
include <numeric>
include <memory>
include <utility>

4: Cpp 库积累

  • xpack: 实现 C++ 结构体和 JSON/XML/BSON 互转的库
  • cpp-httplib: 一个只有头文件的 HTTP/HTTPS library。
  • wondertrader: 一站式的量化交易框架。这是采用 C++ 开发的一站式量化交易框架,支持量化交易过程中的数据清洗、回测分析、实盘交易、运营调度等环节。可用于多账户交易、极速/高频交易、算法交易等场景。

04-MySQL运维常用脚本

一: mysql 命令行连接

  • 安装并启动 mysql 服务;

002-CMakeLists.txt说明

CMakeLists.txt说明

CMakeLists.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# cmake最小版本要求3.21.0
cmake_minimum_required(VERSION 3.21.0)

# 指定project名称为client
project(client)

# set 定义变量
set(PROJ_VERSION v1.0.0)

set(CMAKE_CXX_STANDARD 17)
# 设置编译器编译模式
set(cmake_build_type "Debug")

# include 指令用来载入并运行来自于文件或模块的 CMake 代码
#include()

# 将/usr/include/myincludefolder 和./include添加到头文件搜索路径
#include_directories(/usr/include/myincludefolder ./include)
include_directories(include)

# 将/usr/lib/myincludefolder ./lib添加到库文件搜索路径
#link_directories(/usr/lib/myincludefolder ./lib)

# 设置源文件path
aux_source_directory(src DIR_SRCS)
#aux_source_directory(src/entity DIR_ENTITY)
#source_group(src src/entity)

# 生成共享库,这儿不需要.hpp
#add_library(calculate SHARED hello.cpp)

#添加编译参数 -Wall -std=c++11
add_compile_options(-Wall -std=c++17 -O2) # 注意是大写的O

# 链接共享库,将calculate.so动态库链接到可执行文件main
#target_link_libraries(main calculate)


# 添加需要的所有的执行文件
add_executable(client ${DIR_SRCS})

# ---------------CMake常用变量----------------
# CMAKE_C_FLAGS #gcc编译选项
# CMAKE_CXX_FLAGS #g++编译选项
# set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") #这表明不会覆盖CMAKE_CXX_FLAGS,而是在它后面追加-std=c++11这个编译选项
#
##设定编译类型为debug,调试时需要选择debug
# set(CMAEK_BUILE_TYPE Debug)
##设定编译类型为release,发布时需要选择release
# set(CMAKE_BUILE_TYPE Release)
#
# CMAKE_BINARY_DIR
# CMAKE_SOURCE_DIR #指定CMakeList.txt所在的路径
# CMAKE_C_COMPILER #指定C编译器
# CMAKE_CXX_COMPILER #指定C++编译器
# EXECUTABLE_OUTPUT_PATH #可执行文件输出的存放路径
# LIBRARY_OUTPUT_PATH #库文件输出的存放路径

037-Linux配置环境变量的6种方法

Linux配置环境变量的6种方法

1: Linux 读取环境变量

  • 在自定义安装软件的时候,经常需要配置环境变量,下面列举出各种对环境变量的配置方法。

035-Linux高效运维工具

[toc]

Linux高效运维工具

1: 系统性能,资源

1.1 top

1.2 htop

  • yum -y install htop

1.3 btop/gotop

1.4 系统资源监控-NMON

NMON 是一种在 AIX 与各种 Linux 操作系统上广泛使用的监控与分析工具.

go zap日志库

go zap日志库笔记

1: 概述

  • go zap 高性能日志库;

2: 创建实例

  • 通过调用 zap.NewProduction()/zap.NewDevelopment()或者 zap.Example()创建一个 Logger。这三个方法的区别在于它将记录的信息不同,参数只能是 string 类型
  • 三种创建方式对比:
  • Example 和 Production 使用的是 json 格式输出,Development 使用行的形式输出;
  • Development:
    • 从警告级别向上打印到堆栈中来跟踪
    • 始终打印包/文件/行(方法)
    • 在行尾添加任何额外字段作为 json 字符串
    • 以大写形式打印级别名称
    • 以毫秒为单位打印 ISO8601 格式的时间戳
  • Development
    • 从警告级别向上打印到堆栈中来跟踪
    • 始终打印包/文件/行(方法)
    • 在行尾添加任何额外字段作为 json 字符串
    • 以大写形式打印级别名称
    • 以毫秒为单位打印 ISO8601 格式的时间戳
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//代码
var log *zap.Logger
log = zap.NewExample()
log, _ := zap.NewDevelopment()
log, _ := zap.NewProduction()
log.Debug("This is a DEBUG message")
log.Info("This is an INFO message")

//Example 输出
{"level":"debug","msg":"This is a DEBUG message"}
{"level":"info","msg":"This is an INFO message"}

//Development 输出
2018-10-30T17:14:22.459+0800 DEBUG development/main.go:7 This is a DEBUG message
2018-10-30T17:14:22.459+0800 INFO development/main.go:8 This is an INFO message

//Production 输出
{"level":"info","ts":1540891173.3190675,"caller":"production/main.go:8","msg":"This is an INFO message"}
{"level":"info","ts":1540891173.3191047,"caller":"production/main.go:9","msg":"This is an INFO message with fields","region":["us-west"],"id":2}
}

3: 格式化输出

  • zap 有两种类型,分别是zap.Loggerzap.SugaredLogger,它们惟一的区别是,我们通过调用主 logger 的. Sugar()方法来获取一个 SugaredLogger,然后使用 SugaredLogger 以 printf 格式记录语句,例如:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var sugarLogger *zap.SugaredLogger

func InitLogger() {
logger, _ := zap.NewProduction()
sugarLogger = logger.Sugar()
}

func main() {
InitLogger()
defer sugarLogger.Sync()
sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
}

4: 写入文件

  • 默认情况下日志都会打印到应用程序的 console 界面,但是为了方便查询,可以将日志写入文件,但是我们不能再使用前面创建实例的 3 个方法,而是使用zap.New()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
	"os"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var log *zap.Logger

func main() {
	writeSyncer, _ := os.Create("./info.log")         //日志文件存放目录
	encoderConfig := zap.NewProductionEncoderConfig() //指定时间格式
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoder := zapcore.NewConsoleEncoder(encoderConfig)               //获取编码器,NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
	log = zap.New(core, zap.AddCaller())                              //AddCaller()为显示文件名和行号
	log.Info("hello world")
	log.Error("hello world")
}


// log输出结果:
2020-12-16T17:53:30.466+0800 INFO geth/main.go:18 hello world
2020-12-16T17:53:30.486+0800 ERROR geth/main.go:19 hello world

5: 同时输出控制台和文件

  • 如果需要同时输出控制台和文件,只需要改造一下 zapcore.NewCore 即可,示例:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var log *zap.Logger

func main() {
	// 获取编码器,NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 文件writeSyncer
	fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./info.log", //日志文件存放目录
		MaxSize:    1,            //文件大小限制,单位MB
		MaxBackups: 5,            //最大保留日志文件数量
		MaxAge:     30,           //日志文件保留天数
		Compress:   false,        //是否压缩处理
	})
	fileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(fileWriteSyncer, zapcore.AddSync(os.Stdout)), zapcore.DebugLevel) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

	log = zap.New(fileCore, zap.AddCaller()) //AddCaller()为显示文件名和行号

	log.Info("hello world")
	log.Error("hello world")
}

6: 文件切割

  • 日志文件会随时间越来越大,为了避免日志文件把硬盘空间占满,需要按条件对日志文件进行切割,zap 包本身不提供文件切割的功能,但是可以用 zap 官方推荐的lumberjack包处理
1
2
3
4
5
6
7
8
//文件writeSyncer
fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: "./info.log", //日志文件存放目录,如果文件夹不存在会自动创建
MaxSize: 1, //文件大小限制,单位MB
MaxBackups: 5, //最大保留日志文件数量
MaxAge: 30, //日志文件保留天数
Compress: false, //是否压缩处理
})

7: 按级别写入文件

  • 为了管理人员的查询方便,一般我们需要将低于 error 级别的放到 info.log,error 及以上严重级别日志存放到 error.log 文件中,我们只需要改造一下 zapcore.NewCore 方法的第 3 个参数,然后将文件 WriteSyncer 拆成 info 和 error 两个即可,示例:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main

import (
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var log *zap.Logger

func main() {
	var coreArr []zapcore.Core

	// 获取编码器
	encoderConfig := zap.NewProductionEncoderConfig()            //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder        //指定时间格式
	encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
	// encoderConfig.EncodeCaller = zapcore.FullCallerEncoder //显示完整文件路径
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 日志级别
	highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //error级别
		return lev >= zap.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //info和debug级别,debug级别是最低的
		return lev < zap.ErrorLevel && lev >= zap.DebugLevel
	})

	// info文件writeSyncer
	infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/info.log", //日志文件存放目录,如果文件夹不存在会自动创建
		MaxSize:    1,                //文件大小限制,单位MB
		MaxBackups: 5,                //最大保留日志文件数量
		MaxAge:     30,               //日志文件保留天数
		Compress:   false,            //是否压缩处理
	})
	infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

	// error文件writeSyncer
	errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/error.log", //日志文件存放目录
		MaxSize:    1,                 //文件大小限制,单位MB
		MaxBackups: 5,                 //最大保留日志文件数量
		MaxAge:     30,                //日志文件保留天数
		Compress:   false,             //是否压缩处理
	})
	errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer, zapcore.AddSync(os.Stdout)), highPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

	coreArr = append(coreArr, infoFileCore)
	coreArr = append(coreArr, errorFileCore)
	log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller()为显示文件名和行号,可省略

	log.Info("hello info")
	log.Debug("hello debug")
	log.Error("hello error")
}
  • 这样修改之后,info 和 debug 级别的日志就存放到 info.log,error 级别的日志单独放到 error.log 文件中了

8: 控制台按级别显示颜色

  • 指定编码器的 EncodeLevel 即可
1
2
3
4
5
//获取编码器
encoderConfig := zap.NewProductionEncoderConfig() //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
encoder := zapcore.NewConsoleEncoder(encoderConfig)

9: 显示文件路径和行号

  • 前面说到要显示文件路径和行号,只需要 zap.New 方法添加参数 zap.AddCaller()即可,如果要显示完整的路径,需要在编码器配置中指定
1
2
3
4
5
6
//获取编码器
encoderConfig := zap.NewProductionEncoderConfig() //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder //显示完整文件路径
encoder := zapcore.NewConsoleEncoder(encoderConfig)

10: 完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

import (
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var log *zap.Logger

func main() {
	var coreArr []zapcore.Core

	// 获取编码器
	encoderConfig := zap.NewProductionEncoderConfig()            //NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder        //指定时间格式
	encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了
	// encoderConfig.EncodeCaller = zapcore.FullCallerEncoder //显示完整文件路径
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	// 日志级别
	highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //error级别
		return lev >= zap.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //info和debug级别,debug级别是最低的
		return lev < zap.ErrorLevel && lev >= zap.DebugLevel
	})

	// info文件writeSyncer
	infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/info.log", //日志文件存放目录,如果文件夹不存在会自动创建
		MaxSize:    2,                //文件大小限制,单位MB
		MaxBackups: 100,              //最大保留日志文件数量
		MaxAge:     30,               //日志文件保留天数
		Compress:   false,            //是否压缩处理
	})
	infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
	// error文件writeSyncer
	errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./log/error.log", //日志文件存放目录
		MaxSize:    1,                 //文件大小限制,单位MB
		MaxBackups: 5,                 //最大保留日志文件数量
		MaxAge:     30,                //日志文件保留天数
		Compress:   false,             //是否压缩处理
	})
	errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer, zapcore.AddSync(os.Stdout)), highPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志

	coreArr = append(coreArr, infoFileCore)
	coreArr = append(coreArr, errorFileCore)
	log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller()为显示文件名和行号,可省略

	log.Info("hello info")
	log.Debug("hello debug")
	log.Error("hello error")
}