从零实现一个分布式日志平台:ELK的核心设计

前言

在微服务架构中,几十个服务的日志分散在不同的服务器上。出问题时,你需要登录每台服务器、用grep慢慢翻日志------太慢了。

ELK(Elasticsearch + Logstash + Kibana)是分布式日志聚合的经典方案。

今天我们从零实现一个完整的日志平台:

· 日志采集(Filebeat风格)

· 日志传输(Kafka模拟)

· 日志解析(Logstash风格)

· 日志存储(Elasticsearch风格)

· 日志搜索(全文检索)

· 日志可视化(Kibana风格)


一、日志平台核心原理

  1. 架构图

```

┌─────────────────────────────────────────────────────────────┐

│ 应用服务 │

│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │

│ │ 服务A │ │ 服务B │ │ 服务C │ │

│ │ 日志文件 │ │ 日志文件 │ │ 日志文件 │ │

│ └─────────────┘ └─────────────┘ └─────────────┘ │

└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐

│ Beats (日志采集) │

│ 监控日志文件,实时发送 │

└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐

│ Logstash (日志处理) │

│ 解析/过滤/格式化 │

└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐

│ Elasticsearch (日志存储) │

│ 索引/搜索 │

└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐

│ Kibana (可视化) │

│ 查询/Dashboard │

└─────────────────────────────────────────────────────────────┘

```

  1. 核心组件

组件 功能

Beats 轻量级日志采集

Logstash 日志解析和过滤

Elasticsearch 日志存储和搜索

Kibana 日志可视化


二、完整代码实现

  1. 基础数据结构

```c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <pthread.h>

#include <time.h>

#include <errno.h>

#include <dirent.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <ctype.h>

#define MAX_LOG_LINE 8192

#define MAX_INDEX_NAME 64

#define MAX_FIELD_NAME 64

#define MAX_TOKENS 100

#define MAX_LOGS 100000

// 日志条目

typedef struct log_entry {

char timestamp32;

char level16;

char service64;

char messageMAX_LOG_LINE;

char trace_id32;

char span_id16;

char host32;

char ip16;

long long offset;

time_t timestamp_sec;

struct log_entry *next;

} log_entry_t;

// 索引映射

typedef struct index_mapping {

char name64;

char type16;

int indexed;

} index_mapping_t;

// 索引定义

typedef struct index_definition {

char nameMAX_INDEX_NAME;

index_mapping_t mappings20;

int mapping_count;

int shard_count;

int replica_count;

struct index_definition *next;

} index_definition_t;

// 倒排索引(术语→文档ID列表)

typedef struct inverted_entry {

char term64;

long long *doc_ids;

int doc_count;

int capacity;

struct inverted_entry *next;

} inverted_entry_t;

// 日志存储引擎

typedef struct log_storage {

log_entry_t **logs;

int log_count;

int capacity;

inverted_entry_t *inverted_index;

index_definition_t *indices;

pthread_mutex_t mutex;

char data_dir256;

int current_index_id;

} log_storage_t;

```

  1. 日志采集(Filebeat模拟)

```c

// 采集器配置

typedef struct filebeat_config {

char log_dir256;

char pattern64;

int scan_frequency_ms;

int close_inactive_sec;

} filebeat_config_t;

// 采集器

typedef struct filebeat {

filebeat_config_t config;

FILE *current_file;

char current_path512;

long long file_offset;

pthread_t thread;

int running;

void (*on_event)(log_entry_t *log);

} filebeat_t;

// 创建采集器

filebeat_t *filebeat_create(const char *log_dir, const char *pattern,

void (*on_event)(log_entry_t*)) {

filebeat_t *fb = malloc(sizeof(filebeat_t));

strcpy(fb->config.log_dir, log_dir);

strcpy(fb->config.pattern, pattern);

fb->config.scan_frequency_ms = 1000;

fb->current_file = NULL;

fb->file_offset = 0;

fb->running = 1;

fb->on_event = on_event;

return fb;

}

// 解析日志行

log_entry_t *parse_log_line(const char *line) {

log_entry_t *log = malloc(sizeof(log_entry_t));

memset(log, 0, sizeof(log_entry_t));

char *p = (char*)line;

// 格式1: 2025-01-15T10:30:00Z INFO user-service msg

if (*p == '[') {

p++;

char *end = strchr(p, ']');

if (end) {

int len = end - p;

if (len < 32) {

strncpy(log->timestamp, p, len);

log->timestamplen = '\0';

// 解析时间戳

struct tm tm;

strptime(log->timestamp, "%Y-%m-%dT%H:%M:%S", &tm);

log->timestamp_sec = mktime(&tm);

}

p = end + 2;

}

}

if (*p == '[') {

p++;

char *end = strchr(p, ']');

if (end) {

int len = end - p;

if (len < 16) {

strncpy(log->level, p, len);

log->levellen = '\0';

}

p = end + 2;

}

}

if (*p == '[') {

p++;

char *end = strchr(p, ']');

if (end) {

int len = end - p;

if (len < 64) {

strncpy(log->service, p, len);

log->servicelen = '\0';

}

p = end + 2;

}

}

strcpy(log->message, p);

return log;

}

```

  1. 日志处理(Logstash模拟)

```c

// 过滤器

typedef struct filter {

char field64;

char pattern128;

char replacement128;

int action; // 0: replace, 1: remove, 2: add

struct filter *next;

} filter_t;

// Logstash管道

typedef struct logstash_pipeline {

filter_t *filters;

char grok_pattern256;

int enable_grok;

pthread_mutex_t mutex;

} logstash_pipeline_t;

// 创建管道

logstash_pipeline_t *pipeline_create(void) {

logstash_pipeline_t *p = malloc(sizeof(logstash_pipeline_t));

p->filters = NULL;

p->enable_grok = 1;

pthread_mutex_init(&p->mutex, NULL);

return p;

}

// 添加过滤器

void pipeline_add_filter(logstash_pipeline_t *p, const char *field,

const char *pattern, const char *replacement) {

filter_t *f = malloc(sizeof(filter_t));

strcpy(f->field, field);

strcpy(f->pattern, pattern);

strcpy(f->replacement, replacement);

f->action = 0;

f->next = p->filters;

p->filters = f;

}

// 处理日志(解析和过滤)

void pipeline_process(logstash_pipeline_t *p, log_entry_t *log) {

pthread_mutex_lock(&p->mutex);

filter_t *f = p->filters;

while (f) {

if (strcmp(f->field, "message") == 0) {

// 简单替换

char *pos = strstr(log->message, f->pattern);

if (pos) {

// 替换模式

char new_msgMAX_LOG_LINE;

int prefix_len = pos - log->message;

strncpy(new_msg, log->message, prefix_len);

new_msgprefix_len = '\0';

strcat(new_msg, f->replacement);

strcat(new_msg, pos + strlen(f->pattern));

strcpy(log->message, new_msg);

}

}

f = f->next;

}

pthread_mutex_unlock(&p->mutex);

}

```

  1. 日志存储(Elasticsearch模拟)

```c

// 创建存储

log_storage_t *storage_create(const char *data_dir, int capacity) {

log_storage_t *s = malloc(sizeof(log_storage_t));

s->logs = malloc(sizeof(log_entry_t*) * capacity);

s->log_count = 0;

s->capacity = capacity;

s->inverted_index = NULL;

s->indices = NULL;

s->current_index_id = 0;

pthread_mutex_init(&s->mutex, NULL);

strcpy(s->data_dir, data_dir);

mkdir(data_dir, 0755);

printf("Elasticsearch 存储创建,容量: %d\n", capacity);

return s;

}

// 创建索引

void storage_create_index(log_storage_t *s, const char *name,

int shard_count, int replica_count) {

index_definition_t *idx = malloc(sizeof(index_definition_t));

strcpy(idx->name, name);

idx->shard_count = shard_count;

idx->replica_count = replica_count;

idx->mapping_count = 0;

idx->next = s->indices;

s->indices = idx;

}

// 添加映射

void storage_add_mapping(index_definition_t *idx, const char *name,

const char *type, int indexed) {

if (idx->mapping_count >= 20) return;

strcpy(idx->mappingsidx-\>mapping_count.name, name);

strcpy(idx->mappingsidx-\>mapping_count.type, type);

idx->mappingsidx-\>mapping_count.indexed = indexed;

idx->mapping_count++;

}

// 分词

int tokenize_text(const char *text, char tokens\[\]64) {

int count = 0;

char buffer1024;

strcpy(buffer, text);

char *p = buffer;

while (*p && count < MAX_TOKENS) {

while (*p && !isalnum(*p)) p++;

if (!*p) break;

char *start = p;

while (*p && (isalnum(*p) || *p == '_' || *p == '.' || *p == '-')) p++;

int len = p - start;

if (len < 64) {

strncpy(tokenscount, start, len);

tokenscountlen = '\0';

for (int i = 0; i < len; i++) {

tokenscounti = tolower(tokenscounti);

}

count++;

}

}

return count;

}

// 索引日志

void storage_index(log_storage_t *s, log_entry_t *log) {

pthread_mutex_lock(&s->mutex);

if (s->log_count >= s->capacity) {

free(s->logs0);

for (int i = 1; i < s->log_count; i++) {

s->logsi-1 = s->logsi;

}

s->log_count--;

}

s->logss-\>log_count++ = log;

long long doc_id = s->log_count - 1;

// 建立倒排索引(对message分词)

char tokensMAX_TOKENS64;

int token_count = tokenize_text(log->message, tokens);

for (int i = 0; i < token_count; i++) {

inverted_entry_t *entry = s->inverted_index;

int found = 0;

while (entry) {

if (strcmp(entry->term, tokensi) == 0) {

found = 1;

break;

}

entry = entry->next;

}

if (found) {

if (entry->doc_count >= entry->capacity) {

entry->capacity *= 2;

entry->doc_ids = realloc(entry->doc_ids,

sizeof(long long) * entry->capacity);

}

entry->doc_idsentry-\>doc_count++ = doc_id;

} else {

entry = malloc(sizeof(inverted_entry_t));

strcpy(entry->term, tokensi);

entry->doc_ids = malloc(sizeof(long long) * 10);

entry->doc_count = 1;

entry->capacity = 10;

entry->doc_ids0 = doc_id;

entry->next = s->inverted_index;

s->inverted_index = entry;

}

}

pthread_mutex_unlock(&s->mutex);

}

```

  1. 日志搜索

```c

// 搜索日志

log_entry_t **storage_search(log_storage_t *s, const char *query,

int from, int size, int *total) {

pthread_mutex_lock(&s->mutex);

char tokensMAX_TOKENS64;

int token_count = tokenize_text(query, tokens);

if (token_count == 0) {

*total = 0;

pthread_mutex_unlock(&s->mutex);

return NULL;

}

// 收集匹配的文档ID

long long *matched_docs = NULL;

int matched_count = 0;

for (int i = 0; i < s->log_count; i++) {

int match = 1;

for (int j = 0; j < token_count; j++) {

inverted_entry_t *entry = s->inverted_index;

int found = 0;

while (entry) {

if (strcmp(entry->term, tokensj) == 0) {

for (int k = 0; k < entry->doc_count; k++) {

if (entry->doc_idsk == i) {

found = 1;

break;

}

}

break;

}

entry = entry->next;

}

if (!found) {

match = 0;

break;

}

}

if (match) {

matched_docs = realloc(matched_docs, sizeof(long long) * (matched_count + 1));

matched_docsmatched_count++ = i;

}

}

*total = matched_count;

// 分页

int start = from < matched_count ? from : matched_count;

int end = (from + size) < matched_count ? (from + size) : matched_count;

int result_count = end - start;

log_entry_t **results = malloc(sizeof(log_entry_t*) * (result_count + 1));

for (int i = 0; i < result_count; i++) {

resultsi = s->logsmatched_docs\[start + i];

}

resultsresult_count = NULL;

free(matched_docs);

pthread_mutex_unlock(&s->mutex);

return results;

}

```

  1. 测试代码

```c

void test_elk() {

printf("=== ELK日志平台测试 ===\n\n");

// 创建存储

log_storage_t *storage = storage_create("./elk_data", 10000);

storage_create_index(storage, "logstash-2025.01.15", 3, 1);

// 创建管道

logstash_pipeline_t *pipeline = pipeline_create();

pipeline_add_filter(pipeline, "message", "password", "REDACTED");

// 生成日志

printf("索引日志...\n");

const char *log_lines\[\] = {

"2025-01-15T10:30:00Z INFO user-service User login successful user_id=12345",

"2025-01-15T10:30:05Z ERROR order-service Database connection failed timeout=30s",

"2025-01-15T10:30:10Z WARN payment-service Payment gateway slow took=2500ms",

"2025-01-15T10:30:15Z INFO user-service User logout user_id=12345",

"2025-01-15T10:30:20Z ERROR user-service Invalid password attempt from 192.168.1.100"

};

for (int i = 0; i < 5; i++) {

log_entry_t *log = parse_log_line(log_linesi);

pipeline_process(pipeline, log);

storage_index(storage, log);

}

// 搜索测试

printf("\n搜索 'ERROR':\n");

int total;

log_entry_t **results = storage_search(storage, "ERROR", 0, 10, &total);

printf(" 找到 %d 条\n", total);

for (int i = 0; resultsi; i++) {

printf(" %s %s\n", resultsi->level, resultsi->message);

}

free(results);

printf("\n搜索 'user-service':\n");

results = storage_search(storage, "user-service", 0, 10, &total);

printf(" 找到 %d 条\n", total);

for (int i = 0; resultsi; i++) {

printf(" %s %s\n", resultsi->level, resultsi->message);

}

free(results);

printf("\n搜索 'Database connection failed':\n");

results = storage_search(storage, "Database connection failed", 0, 10, &total);

printf(" 找到 %d 条\n", total);

for (int i = 0; resultsi; i++) {

printf(" %s %s\n", resultsi->level, resultsi->message);

}

free(results);

printf("\n索引统计: %d 条日志\n", storage->log_count);

free(storage);

free(pipeline);

}

int main() {

test_elk();

return 0;

}

```


三、编译和运行

```bash

gcc -o elk elk.c -lpthread

./elk

```


四、ELK vs 本实现

特性 本实现 ELK

日志采集 ✅ ✅ Filebeat

日志处理 ✅ ✅ Logstash

倒排索引 ✅ ✅ Elasticsearch

全文搜索 ✅ ✅

可视化 ❌ ✅ Kibana

分布式 ❌ ✅

集群支持 ❌ ✅


五、总结

通过这篇文章,你学会了:

· ELK的核心架构(Beats + Logstash + Elasticsearch + Kibana)

· 日志采集(Filebeat模拟)

· 日志处理(Logstash模拟)

· 倒排索引构建

· 全文搜索实现

· 日志管道处理

ELK是日志聚合的经典方案。掌握它,你就理解了分布式日志平台的核心设计。

下一篇预告:《从零实现一个分布式监控:Grafana的核心设计》


评论区分享一下你用ELK解决过什么场景~

相关推荐
神仙别闹1 小时前
基于QT(C++)实现Windows 自启动项查看和分析
c++·windows·qt
WWTYYDS_6662 小时前
ProtoBuf超详细使用教程
c++
野生风长2 小时前
c++类和对象(this指针,重载operator,习题总结)
java·开发语言·c++
雪的季节2 小时前
Python「假多态」与 C++「真多态」的核心区别
开发语言·c++
zmzb01033 小时前
C++课后习题训练记录Day166
开发语言·c++
皓月斯语3 小时前
程序设计语言的特点
开发语言·数据结构·c++
邪修king3 小时前
C++ 进阶终章:异常机制与智能指针全解 —— 从错误处理到 RAII 资源管理,打通现代 C++ 的核心命脉
android·数据结构·c++
梓䈑9 小时前
【算法题攻略】BFS 解决FloodFill 算法、最短路问题、多源BFS 和 拓扑排序
c++·算法·宽度优先
程序喵大人13 小时前
【C++进阶】STL容器与迭代器 - 01 STL 容器先解决元素放在哪里
开发语言·c++·stl