Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

jobs:
Tests:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
fail-fast: false

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- name: Build and Test
shell: bash
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
sudo apt-get update && sudo apt-get install -y cmake build-essential libgtest-dev
fi

cmake -B build
cmake --build build
cd build/tests
./test-runner
21 changes: 16 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,22 @@ project(LogAnalyzer VERSION 0.1.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
cmake_policy(SET CMP0079 NEW)


find_package(GTest QUIET)
if(NOT GTest_FOUND)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)

FetchContent_MakeAvailable(googletest)
endif()


add_subdirectory(core)
add_subdirectory(cli)

find_package(GTest)
if(GTest_FOUND)
add_subdirectory(tests)
endif()
add_subdirectory(tests)
59 changes: 44 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,54 @@
# Log Analyzer v0.1.0
# Log Analyzer v0.2.0

## 💻 Платформы
- ✅ macOS
- ❌ Windows (в разработке)
- ❌ Linux (в разработке)
- ✅ macOS (clang/gcc, CMake ≥ 3.10)
- ✅ Linux (gcc, CMake ≥ 3.10)

## 🚀 Возможности
- Чтение Nginx access логов
- Базовый анализ: IP, ошибки, временные метрики
- Консольный интерфейс
### 📋 Поддерживаемые форматы логов:
- Nginx
- Apache

### 📊 Анализируемая статистика:
- Общая статистика запросов (успешные/ошибочные)
- Распределение по форматам логов
- Топ IP-адресов с ошибками (топ-10)
- Типы HTTP-ошибок (400, 401 и т.д.)
- Временное распределение ошибок (по часам)
- Количество неудачных парсингов

## 🛠️ Технологический стек
- Язык: C++17
- Сборка: CMake
- Тестирование: Google Test (gtest/gmock)
- CI: Сборка и тестирование

## 🛠️ В разработке:
- CI/CD
- GUI
- Gcov
- Автоопределение формата
- Apache Access Logs

## 📦 Установка
```bash
## 📦 Установка и сборка
- Клонирование репозитория
```
git clone https://github.com/Arkilleru/log-analyzer.git
cd log-analyzer
mkdir build && cd build
cmake .. && make
```

- Сборка
```
./run.sh build
```

- Тестирование
```
./run.sh test
```

- Очистка
```
./run.sh clean
```

- Очистка + Сборка + Тестирование
```
./run.sh all
```
1 change: 1 addition & 0 deletions cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

int main() {
std::string path;
std::cout << "Enter log file path:\n";
std::getline(std::cin, path);

Analyzer analysis;
Expand Down
17 changes: 8 additions & 9 deletions core/analyzer.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
#include "analyzer.h"
#include <iostream>

std::string Analyzer::analyze(std::string& path) {
reader.OpenFile(path);
std::string Analyzer::analyze(std::string path) {
reader_.OpenFile(path);

AnalysisResult res;
while(reader.MoreLines()) {
std::string str = reader.ReadLine();
LogInformation data = parser.Parse(str);
statistics.Process(data, res);
while(reader_.MoreLines()) {
std::string str = reader_.ReadLine();
LogInformation data = parser_.Parse(str);
statistics_.Process(data, res);
}

reader.CloseFile();
std::string report = reporter.GenerateTextReport(res);
reader_.CloseFile();
std::string report = reporter_.GenerateTextReport(res);

return report;
}
88 changes: 9 additions & 79 deletions core/analyzer.h
Original file line number Diff line number Diff line change
@@ -1,89 +1,19 @@
#pragma once

#include "reader.h"
#include "parser.h"
#include "statistics.h"
#include "reporter.h"
#include <string>
#include <fstream>
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <regex>

struct LogInformation {
std::string ip;
std::string time;
std::string operation;
std::string url;
int status;
size_t answer_size;
bool parse_success;
};

struct AnalysisResult {
std::unordered_map<std::string, int> error_counts;
std::unordered_map<std::string, int> ip;
std::unordered_map<std::string, int> time_distribution;
int total_processed = 0;
int failed_parses = 0;
int successful_requests = 0;
int error_requests = 0;
};



class Parser {
private:
std::regex nginx_pattern;
public:
Parser() : nginx_pattern(R"(^(\d+\.\d+\.\d+\.\d+) - - \[(.*?)\] \"(\w+) (.*?) HTTP/.*?\" (\d+) (\d+))") {}

LogInformation Parse(const std::string& line);
};


class Reporter {
private:
std::string GenerateGeneralStats(const AnalysisResult& data);
std::string GenerateIpErrorsTop(const AnalysisResult& data);
std::string GenerateErrorTypes(const AnalysisResult& data);
std::string GenerateTimeDistribution(const AnalysisResult& data);
std::vector<std::pair<std::string, int>> ToSortedVector(const std::unordered_map<std::string, int>& mp);
public:
std::string GenerateTextReport(const AnalysisResult& data);
};


class Statistics {
public:
void Process(LogInformation& data, AnalysisResult& res);
std::string ExtractHour(std::string& time);

};


class Reader {
private:
std::unique_ptr<std::ifstream> file;
size_t line_number;

public:
Reader() = default;
~Reader() = default;

bool OpenFile(const std::string& path);
std::string ReadLine();
bool MoreLines();
void CloseFile();
};



class Analyzer {
private:
Reader reader;
Parser parser;
Reporter reporter;
Statistics statistics;
Reader reader_;
Parser parser_;
Reporter reporter_;
Statistics statistics_;

public:
std::string analyze(std::string& path);
std::string analyze(std::string path);
};

41 changes: 41 additions & 0 deletions core/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include <unordered_map>
#include <string>
#include <algorithm>

enum class LogFormat {
Nginx,
Apache,
Unknown
};

inline std::string LogFormatToString(LogFormat format) {
switch(format) {
case LogFormat::Apache: return "Apache";
case LogFormat::Nginx: return "Nginx";
default: return "Unknow";
}
}

struct LogInformation {
std::string ip;
std::string time;
std::string operation;
std::string url;
int status;
size_t answer_size;
bool parse_success;
LogFormat format;
};

struct AnalysisResult {
std::unordered_map<std::string, int> error_counts;
std::unordered_map<std::string, int> ip;
std::unordered_map<std::string, int> time_distribution;
std::unordered_map<LogFormat, int> format_counts;
int total_processed = 0;
int failed_parses = 0;
int successful_requests = 0;
int error_requests = 0;
};
36 changes: 30 additions & 6 deletions core/parser.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
#include "analyzer.h"
#include "parser.h"
#include <iostream>

LogFormat Parser::DetectFormat(const std::string& line) {
if (line.find("HTTP/1.0") != std::string::npos) {
return LogFormat::Nginx;

LogInformation Parser::Parse(const std::string& line) {
LogInformation data;
std::smatch matches;
} else if (line.find("HTTP/1.1") != std::string::npos) {
return LogFormat::Apache;
}

if (std::regex_match(line, matches, nginx_pattern)) {
return LogFormat::Unknown;
}

void Parser::RegexParse(const std::string& line, const std::regex& pattern, LogInformation& data) {
std::smatch matches;
if (std::regex_match(line, matches, pattern)) {
data.ip = matches[1];
data.time = matches[2];
data.operation = matches[3];
Expand All @@ -15,10 +23,26 @@ LogInformation Parser::Parse(const std::string& line) {
data.answer_size = std::stoul(matches[6]);
data.parse_success = true;

return data;
return;
}

data.parse_success = false;
}

LogInformation Parser::Parse(const std::string& line) {
LogInformation data;
data.format = DetectFormat(line);

if (data.format == LogFormat::Apache) {
RegexParse(line, apache_pattern_, data);
}
else if (data.format == LogFormat::Nginx) {
RegexParse(line, nginx_pattern_, data);
}
else {
data.parse_success = false;
}


return data;
}
20 changes: 20 additions & 0 deletions core/parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once
#include "common.h"

#include <regex>

class Parser {
private:
std::regex nginx_pattern_;
std::regex apache_pattern_;
public:
Parser() {
nginx_pattern_ = R"(^(\d+\.\d+\.\d+\.\d+) - - \[([^\]]+)\] \"(\w+) (.*?) HTTP/1.0" (\d+) (\d+))";
apache_pattern_ = R"(^(\d+\.\d+\.\d+\.\d+) - - \[([^\]]+)\] \"(\w+) (.*?) HTTP/1.1" (\d+) (\d+))";
}

LogFormat DetectFormat(const std::string& line);
LogInformation Parse(const std::string& line);
void RegexParse(const std::string& line, const std::regex& pattern, LogInformation& data);

};
Loading