├── .gitignore ├── AUTHORS ├── CMakeLists.txt ├── ChangeLog ├── LICENSE ├── README.md ├── THANKS ├── compat ├── compat.h ├── pledge.c ├── pledge.h ├── strtonum.c └── strtonum.h ├── deps ├── MurmurHash3 │ ├── MurmurHash3.c │ └── MurmurHash3.h └── hll │ ├── LICENSE │ ├── hll.c │ └── hll.h ├── examples ├── logswan.json └── logswan.log ├── logswan.spec ├── man └── logswan.1 ├── src ├── config.c ├── config.h ├── continents.c ├── continents.h ├── countries.c ├── countries.h ├── logswan.c ├── output.c ├── output.h ├── parse.c ├── parse.h └── seccomp.h └── tests ├── invalid.log ├── logswan.log ├── logswan.mmdb └── logswan.pl /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Logswan is developed by: 2 | 3 | Frederic Cambus 4 | 5 | Site: https://www.cambus.net 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Logswan 2.1.15 3 | # Copyright (c) 2015-2025, Frederic Cambus 4 | # https://www.logswan.org 5 | # 6 | # Created: 2015-05-31 7 | # Last Updated: 2025-03-24 8 | # 9 | # Logswan is released under the BSD 2-Clause license. 10 | # See LICENSE file for details. 11 | # 12 | # SPDX-License-Identifier: BSD-2-Clause 13 | # 14 | 15 | cmake_minimum_required(VERSION 3.10) 16 | 17 | set(CMAKE_C_STANDARD 11) 18 | set(CMAKE_C_STANDARD_REQUIRED ON) 19 | set(CMAKE_C_EXTENSIONS OFF) 20 | 21 | project(logswan C) 22 | 23 | include(CheckFunctionExists) 24 | include(GNUInstallDirs) 25 | 26 | # Conditional build options 27 | set(ENABLE_SECCOMP 0 CACHE BOOL "Enable building with seccomp") 28 | 29 | # Check if system has pledge and strtonum 30 | list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_OPENBSD_SOURCE) 31 | check_function_exists(pledge HAVE_PLEDGE) 32 | check_function_exists(strtonum HAVE_STRTONUM) 33 | 34 | if(ENABLE_SECCOMP) 35 | # Check if system has seccomp 36 | message(STATUS "Looking for seccomp") 37 | find_path(SECCOMP NAMES "linux/seccomp.h") 38 | if(SECCOMP) 39 | message(STATUS "Looking for seccomp - found") 40 | add_definitions(-DHAVE_SECCOMP) 41 | else() 42 | message(STATUS "Looking for seccomp - not found") 43 | endif() 44 | endif(ENABLE_SECCOMP) 45 | 46 | # Additional include directories for compat functions + dependencies 47 | include_directories("compat") 48 | include_directories("deps/hll") 49 | 50 | # libmaxminddb 51 | find_path(GEOIP2_INCLUDE_DIRS maxminddb.h) 52 | find_library(GEOIP2_LIBRARIES NAMES maxminddb REQUIRED) 53 | include_directories(${GEOIP2_INCLUDE_DIRS}) 54 | 55 | # Jansson 56 | find_path(JANSSON_INCLUDE_DIRS jansson.h) 57 | find_library(JANSSON_LIBRARIES NAMES jansson REQUIRED) 58 | include_directories(${JANSSON_INCLUDE_DIRS}) 59 | 60 | set(DEPS deps/hll/hll.c deps/MurmurHash3/MurmurHash3.c) 61 | set(SRC src/logswan.c src/config.c src/continents.c src/countries.c 62 | src/output.c src/parse.c) 63 | 64 | if(NOT HAVE_PLEDGE) 65 | set(SRC ${SRC} compat/pledge.c) 66 | endif() 67 | 68 | if(NOT HAVE_STRTONUM) 69 | set(SRC ${SRC} compat/strtonum.c) 70 | endif() 71 | 72 | set(GEOIP2DIR ${CMAKE_INSTALL_PREFIX}/share/dbip 73 | CACHE PATH "Path to GeoIP2 databases") 74 | set(GEOIP2DB "dbip-country-lite.mmdb" CACHE PATH "GeoIP2 database file") 75 | 76 | add_definitions(-D_GNU_SOURCE -Wall -Wextra -pedantic) 77 | add_definitions(-DGEOIP2DIR="${GEOIP2DIR}/") 78 | add_definitions(-DGEOIP2DB="${GEOIP2DB}") 79 | add_executable(logswan ${SRC} ${DEPS}) 80 | 81 | target_link_libraries(logswan ${GEOIP2_LIBRARIES} ${JANSSON_LIBRARIES} m) 82 | 83 | install(TARGETS logswan DESTINATION ${CMAKE_INSTALL_BINDIR}) 84 | install(FILES man/logswan.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/) 85 | 86 | enable_testing() 87 | add_test(logswan logswan) 88 | add_test(usage logswan -h) 89 | add_test(version logswan -v) 90 | add_test(processing logswan ${PROJECT_SOURCE_DIR}/tests/logswan.log) 91 | add_test(invalid logswan ${PROJECT_SOURCE_DIR}/tests/invalid.log) 92 | add_test(geolocation logswan -g -d ${PROJECT_SOURCE_DIR}/tests/logswan.mmdb 93 | ${PROJECT_SOURCE_DIR}/tests/logswan.log) 94 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | Logswan 2.1.15 (2025-03-24) 2 | 3 | - Add link to Gentoo package in the README 4 | - Add initial RPM spec file 5 | - Bump cmake_minimum_required to 3.5, to allow building with CMake 4.0 6 | - Bump cmake_minimum_required to 3.10 to be more future proof 7 | 8 | 9 | 10 | Logswan 2.1.14 (2023-03-30) 11 | 12 | - Add missing void to the usage() function definition 13 | - Move manual page to the man directory 14 | 15 | 16 | 17 | Logswan 2.1.13 (2022-07-28) 18 | 19 | - Remove useless backslashes in usage() 20 | - Add entries with IPv6 remote hosts to exercise the IPv6 code paths 21 | - Add an invalid.log log file with invalid lines, for testing purposes 22 | - Add a few more test cases in CMakeLists.txt 23 | - Also generate data for IPv6 addresses in the MMDB test database 24 | - Regenerate the example MMDB database and JSON example output 25 | 26 | 27 | 28 | Logswan 2.1.12 (2021-12-02) 29 | 30 | - Fix a use-after-free (read) triggered by strcmp(3) calls. 31 | 32 | The parse_request() function didn't zero out the parsed_request struct 33 | between each call. Since the parsing loop was switched to using getline(3) 34 | instead of a fixed size buffer to process log lines, it could reference 35 | already freed memory in certain cases. 36 | 37 | Thanks to Brian Carpenter (@geeknik) for finding and reporting the issue. 38 | 39 | 40 | 41 | Logswan 2.1.11 (2021-11-16) 42 | 43 | - Bump CMake minimum required version to 2.8.12 44 | - Add SPDX short license identifier in source files 45 | - Use CMAKE_C_STANDARD instead of hardcoding compiler flags 46 | - Switch to using getline(3) instead of fgets(3) 47 | - Call hll_init() after doing getopt() processing 48 | 49 | 50 | 51 | Logswan 2.1.10 (2021-02-15) 52 | 53 | - Only call MMDB_close if the GeoIP option was enabled. This fixes a 54 | crash on program exit on OpenBSD when running without the GeoIP 55 | option enabled. 56 | 57 | 58 | 59 | Logswan 2.1.9 (2021-02-15) 60 | 61 | - Stop forcing FORTIFY_SOURCE=2, it should be package builders decision 62 | - Add link to Homebrew package in the README 63 | - Remove unused countryId variable 64 | - Remove dead increments for argc and argv 65 | - Rename variables to get rid of all camelCase occurrences 66 | - Remove the measuring Logswan memory usage section from the README 67 | - Try to harmonize usage information everywhere 68 | - Rename the displayUsage() function to usage() 69 | - Rename all the parse*() functions to use snake_case 70 | - Get rid of global variables, move all declarations to main() 71 | 72 | 73 | 74 | Logswan 2.1.8 (2020-10-19) 75 | 76 | - Make displayUsage() static 77 | - Removing some leading whitespace characters 78 | - Constify methods and protocols names array 79 | - Constify countries and continents names and ID arrays 80 | - Enable FORTIFY_SOURCE level 2 81 | - Call memset() to set all entry_data struct fields to zero 82 | 83 | 84 | 85 | Logswan 2.1.7 (2020-09-17) 86 | 87 | - Add a Perl program to generate an example MMDB database for testing 88 | - Add a new test case to exercise the IP geolocation codepaths 89 | - Add support for seccomp on arm 90 | - Add missing test for __NR_mmap, the mmap syscall doesn't exist on arm 91 | 92 | 93 | 94 | Logswan 2.1.6 (2020-07-03) 95 | 96 | - Validate architectures for seccomp 97 | - Minor code shuffling, for consistency with other codebases 98 | - Add seccomp support on i386, tested on glibc and musl 99 | - Remove entry for "Netherlands Antilles" in country table 100 | - Swaziland was officially renamed to Eswatini in 2018 101 | - Macedonia was officially renamed to North Macedonia in February 2019 102 | - Rename Vatican City to Vatican to be consistent with GeoNames naming 103 | - Rename Aland to Aland Islands to be consistent with GeoNames naming 104 | 105 | 106 | 107 | Logswan 2.1.5 (2020-06-12) 108 | 109 | - Switch the GEOIP2DIR example to point to '/var/db/dbip' 110 | - Use CLOCK_REALTIME if CLOCK_MONOTONIC is not available 111 | - Remove unneeded and includes 112 | - Add missing include 113 | - Remove useless _POSIX_SOURCE define 114 | - Remove useless _POSIX_C_SOURCE 199309L define 115 | - Define _GNU_SOURCE in CMakeLists.txt to avoid cluttering source files 116 | - No need to set HAVE_SECCOMP to 1, defining it is enough 117 | - Remove some unneeded seccomp related includes 118 | 119 | 120 | 121 | Logswan 2.1.4 (2020-03-27) 122 | 123 | - Move GEOIP2DB definition to CMakeLists.txt, allowing build time 124 | customization 125 | - Display database path name in error message 126 | - Remove direct link to GeoLite2 databases 127 | - Add dependencies installation instruction for Fedora 128 | - Change GEOIP2DB default value to "dbip-country-lite.mmdb" 129 | - Change GEOIP2DIR default value to "${CMAKE_INSTALL_PREFIX}/share/dbip" 130 | - Mention that DB-IP IP to Country Lite database is now the recommended option 131 | 132 | 133 | 134 | Logswan 2.1.3 (2020-01-17) 135 | 136 | - Add a new test target, to test log processing 137 | - Move printed statistics after the actual output 138 | - Use OpenBSD style(9) for function prototypes and declarations 139 | - Remove seccomp mention in README as it is currently disabled by default 140 | 141 | 142 | 143 | Logswan 2.1.2 (2019-11-19) 144 | 145 | - Add ENABLE_SECCOMP build option, to allow building seccomp support 146 | conditionally 147 | - Disable seccomp by default, it needs more testing on non !amd64 platforms 148 | - Use ${CMAKE_INSTALL_BINDIR} instead of hardcoding 'bin' 149 | 150 | 151 | 152 | Logswan 2.1.1 (2019-10-30) 153 | 154 | - Check if system has seccomp in CMakeLists.txt 155 | - Use the HAVE_SECCOMP macro to check whether or not to enable seccomp 156 | - Define and use a GEOIP2DB macro to specify GeoLite2 database name 157 | - Add a switch (-d) to allow specifying path to a GeoIP2 database file 158 | - Define and use a LOGSWAN_SYSCALL_ALLOW macro to make code more readable 159 | - Adding missing #include guard in seccomp.h header file 160 | - Use __NR_ instead of SYS_ prefix in LOGSWAN_SYSCALL_ALLOW 161 | - Fix the build on aarch64 Linux, where the open() syscall does not exist 162 | - Add error checking for both prctl() calls 163 | 164 | 165 | 166 | Logswan 2.1.0 (2019-10-23) 167 | 168 | - Add FALLTHROUGH comments where appropriate 169 | - Add support for parsing HTTP/3 requests 170 | - Add initial seccomp support on Linux, tested on musl and glibc systems 171 | 172 | 173 | 174 | Logswan 2.0.4 (2019-08-16) 175 | 176 | - Adding #include guard in compat header file 177 | - Add an example log file and regenerate output example 178 | - Add dependencies installation instructions for NetBSD and FreeBSD 179 | - Add final dots for options descriptions 180 | - Add final dot when printing results summary 181 | - Use EXIT_SUCCESS and EXIT_FAILURE macros for return values 182 | - Add a trailing newline when printing JSON output 183 | 184 | 185 | 186 | Logswan 2.0.3 (2018-10-15) 187 | 188 | - Use -std=c11, Logswan requires a C11 compiler for libmaxminddb 189 | - Enable support for parsing HTTP/2.0 requests, for real this time 190 | - Revert back to using INT64_MAX for strtonum() maxval, as maxval is long long 191 | and using UINT64_MAX caused bandwidth parsing to always fail (Thanks James Loh 192 | for reporting the issue) 193 | - Move maps of countries and continents to separate files 194 | 195 | 196 | 197 | Logswan 2.0.2 (2018-08-05) 198 | 199 | - Use UINT64_MAX for strtonum() maxval 200 | - Add missing headers and reorder includes 201 | - Add support for HTTP/2.0 202 | 203 | 204 | 205 | Logswan 2.0.1 (2018-06-27) 206 | 207 | - Do not use -Werror by default 208 | - Do not always call inet_pton two times per log line, this speeds things up 209 | - Use bool types for isIPv4 and isIPv6 210 | - Use the monotonic clock to determine runtime 211 | - Move conditional includes for 'pledge' and 'strtonum' in compat.h 212 | 213 | 214 | 215 | Logswan 2.0.0 (2018-03-16) 216 | 217 | - Use type off_t for results struct member fileSize 218 | - Reflect OpenBSD's pledge() changes 219 | - Switch to using libmaxminddb and GeoIP2/GeoLite2 databases 220 | - Add Antarctica to the list of continents 221 | 222 | 223 | 224 | Logswan 1.07 (2017-02-14) 225 | 226 | - Harmonize arrays names 227 | - Remove array of months, it's currently unused and will likely remain so 228 | - Simplify internal JSON array and object names 229 | - Use OpenBSD style(9) for function prototypes and declarations 230 | - Revert back to using strtok, at least for now 231 | - Do not use EXIT_SUCCESS and EXIT_FAILURE macros anymore 232 | - Fix implicit function declaration error on NetBSD (Thanks Maya Rashish) 233 | - Remove now useless variables initialization and unnecessary includes 234 | - Do not add an extra new line when displaying usage or version 235 | 236 | 237 | 238 | Logswan 1.06 (2016-12-17) 239 | 240 | - Relicensed under the BSD 2-Clause license 241 | - Use strtok_r instead of strtok to tokenize lines 242 | - Do not attempt to increment countries and continents arrays if there 243 | is no GeoIP database loaded 244 | - Harmonize variable names for the GeoIP databases 245 | - GeoIP lookups are now disabled by default (add a -g switch to enable) 246 | - Use fstat on open file descriptor instead of using stat before opening 247 | the input file 248 | - Count the log line as invalid if parsedLine.remoteHost is NULL 249 | - Pass results structure by reference, not by value 250 | - Initialize some uninitialized variables 251 | - Headers cleanup 252 | 253 | 254 | 255 | Logswan 1.05 (2016-02-25) 256 | 257 | - Documentation update (notes on measuring Logswan memory usage) 258 | - Add additional include directories for compat functions + dependencies 259 | to avoid using relative path in includes 260 | - Check that *lineBuffer is not NUL before attempting to parse log line 261 | - Perform GeoIP lookup and HLL add in the same if block 262 | - Increment IPv4 and IPv6 hits counters individually and conditionally 263 | - Use CMake to check if the system has OpenBSD's pledge available and 264 | link pledge conditionally using a null implementation when compiled 265 | on non OpenBSD systems 266 | - Adding an array of months, for the upcoming split log functionalities 267 | 268 | 269 | 270 | Logswan 1.04 (2016-01-10) 271 | 272 | - Moving global variables into main 273 | - Using 'size_t' instead of 'int' for array indexes in for loops 274 | - Using 'uint32_t' for all non 'uint64_t' integers 275 | - Do not increment hits and processed lines counter for each parsed line, 276 | compute total only once when everything is parsed 277 | - Setting 'CMAKE_BUILD_TYPE' to 'Release' and formatting fixes 278 | - Sanitize CMake script to build under NetBSD (Thanks Kamil Rytarowski) 279 | - Initializing some uninitialized variables 280 | - Renaming 'DATADIR' variables to 'GEOIPDIR' 281 | 282 | 283 | 284 | Logswan 1.03 (2016-01-01) 285 | 286 | - Remove header display and do not print name of processed file 287 | - Print results to stderr instead of stdout 288 | - Output JSON data to stdout instead of creating a new file 289 | - Define GeoIP databases path in CMakeLists.txt 290 | - Adding log file name in the JSON output 291 | - Removing some hardcoded values and replacing them with constants 292 | defined in config.h 293 | - Breaking the loop when a match is found in the request parser 294 | - Using enumeration constants instead of macros 295 | - Process GeoIP continent information 296 | - Re-ordering protocols and methods with more common occurrences on top of 297 | the list, allowing to break earlier when iterating through the array 298 | - Adding support for reading logs from standard input 299 | - Renaming 'definitions' files to 'config' 300 | - Increasing countries array size, as an attempt to be future-proof 301 | - Initial support for using pledge() on OpenBSD 302 | - Documentation updates (HLL precision, Features list, GeoIP databases) 303 | - Updated JSON output example 304 | - Added a manual page 305 | 306 | 307 | 308 | Logswan 1.02 (2015-11-02) 309 | 310 | - Renaming 'resource' variable to 'request' in the 'logLine' struct 311 | - Do not attempt to parse empty date tokens 312 | - Do not attempt to parse empty request tokens (Thanks Brian Carpenter for 313 | reporting the issue) 314 | 315 | 316 | 317 | Logswan 1.01 (2015-10-01) 318 | 319 | - Documentation updates 320 | - Fixing segfault when request data is empty or malformed (Thanks Jonathan 321 | Armani for reporting and proposing a fix) 322 | 323 | 324 | 325 | Logswan 1.00 (2015-09-28) 326 | 327 | - Initial release 328 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2025, Frederic Cambus 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 18 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | _____ 3 | .xiX*****Xix. 4 | .X7' '4Xk, 5 | dXl 'XX. . 6 | xXXl XXl . 7 | 4XXX XX' 8 | . ,x iX' _,,xxii 9 | | ²| ,iX7,xiiXXXXXXXl 10 | | .xi,xiXXXXXXXXXXXX: 11 | . ..iXXiXXXXXXXXXXXXXXX7. 12 | . .xXXXXXXXXXXXXXXX'XXXX7 . 13 | | ,XXXXXXXXXXXXXXXX'XXX7' | 14 | : .XXXXX7*'"' 2XXX7'XX7' | 15 | __/ \ _____ ____ \XX' _____ 47' ___ ___ _____ __ 16 | .\\_ \___/ _ \__/ _/_______\ _/______/ / \ \____/ _ \___/ \ _____ 17 | . / __ Y _ __ \__ _________ _____ \/\/ ____ _ _ ______ \/ __/// 18 | :/ / | \ |' \/ \/ \/ \/ Y \/ \ \ : 19 | |\______/\_________/____| /\____ /\_____/\_____/\____|____/\____\___/ | 20 | +--------------------- \____/ --- \____/ ----:----------------------h7/dS!----+ 21 | . | : 22 | : . : | 23 | | . Logswan . | 24 | | : . | 25 | |_|_______________________|__| 26 | | : 27 | . 28 | ``` 29 | # Logswan 30 | 31 | Logswan is a fast Web log analyzer using probabilistic data structures. It is 32 | targeted at very large log files, typically APIs logs. It has constant memory 33 | usage regardless of the log file size, and takes approximatively 4MB of RAM. 34 | 35 | Unique visitors counting is performed using two HyperLogLog counters (one for 36 | IPv4, and another one for IPv6), providing a relative accuracy of 0.10%. 37 | String representations of IP addresses are used and preferred as they offer 38 | better precision. 39 | 40 | Project design goals include: speed, memory-usage efficiency, and keeping the 41 | code as simple as possible. 42 | 43 | Logswan is **opinionated software**: 44 | 45 | - It only supports the Common Log Format, in order to keep the parsing code 46 | simple. It can of course process the Combined Log Format as well (referer 47 | and user agent fields will be discarded) 48 | - It does not split results per day, but log files can be split prior to 49 | being processed 50 | - Input file size and bandwidth usage are reported in bytes, there are no 51 | plans to format or round them 52 | 53 | Logswan is written with security in mind and is running sandboxed on OpenBSD 54 | (using pledge). Experimental seccomp support is available for selected 55 | architectures and can be enabled by setting the `ENABLE_SECCOMP` variable 56 | to `1` when invoking CMake. It has also been extensively fuzzed using AFL 57 | and Honggfuzz. 58 | 59 | ## Features 60 | 61 | Currently implemented features: 62 | 63 | - Counting used bandwidth 64 | - Counting number of processed lines / invalid lines 65 | - Counting number of hits (IPv4 and IPv6 hits) 66 | - Counting visits (unique IP addresses for both IPv4 and IPv6) 67 | - GeoIP lookups (for both IPv4 and IPv6) 68 | - Hourly hits distribution 69 | - HTTP method distribution 70 | - HTTP protocol distribution 71 | - HTTP status codes distribution 72 | 73 | ## Dependencies 74 | 75 | Logswan uses the `CMake` build system and requires `Jansson` and `libmaxminddb` 76 | libraries and header files. 77 | 78 | ## Installing dependencies 79 | 80 | - OpenBSD: `pkg_add -r cmake jansson libmaxminddb` 81 | - NetBSD: `pkgin in cmake jansson libmaxminddb` 82 | - FreeBSD: `pkg install cmake jansson libmaxminddb` 83 | - macOS: `brew install cmake jansson libmaxminddb` 84 | - Alpine Linux: `apk add cmake gcc make musl-dev jansson-dev libmaxminddb-dev` 85 | - Debian / Ubuntu: `apt-get install build-essential cmake libjansson-dev libmaxminddb-dev` 86 | - Fedora: `dnf install cmake gcc make jansson-devel libmaxminddb-devel` 87 | 88 | ## Building 89 | 90 | mkdir build 91 | cd build 92 | cmake .. 93 | make 94 | 95 | Logswan has been successfully built and tested on OpenBSD, NetBSD, FreeBSD, 96 | macOS, and Linux with both Clang and GCC. 97 | 98 | ## Packages 99 | 100 | Logswan packages are available for: 101 | 102 | - [OpenBSD][1] 103 | - [NetBSD][2] 104 | - [FreeBSD][3] 105 | - [Debian][4] 106 | - [Ubuntu][5] 107 | - [Void Linux][6] 108 | - [Gentoo][7] 109 | - [Homebrew][8] 110 | 111 | ### GeoIP2 databases 112 | 113 | Logswan looks for GeoIP2 databases in `${CMAKE_INSTALL_PREFIX}/share/dbip` by 114 | default, which points to `/usr/local/share/dbip`. 115 | 116 | A custom directory can be set using the `GEOIP2DIR` variable when invoking 117 | CMake: 118 | 119 | cmake -DGEOIP2DIR=/var/db/dbip . 120 | 121 | The free Creative Commons licensed DB-IP IP to Country Lite database can be 122 | downloaded [here][9]. 123 | 124 | Alternatively, GeoLite2 Country database from MaxMind can be downloaded free 125 | of charge [here][10], but require accepting an EULA and is not freely licensed. 126 | 127 | ## Usage 128 | 129 | logswan [-ghv] [-d db] logfile 130 | 131 | If file is a single dash (`-'), logswan reads from the standard input. 132 | 133 | The options are as follows: 134 | 135 | -d db Specify path to a GeoIP database. 136 | -g Enable GeoIP lookups. 137 | -h Display usage. 138 | -v Display version. 139 | 140 | Logswan outputs JSON data to **stdout**. 141 | 142 | ## License 143 | 144 | Logswan is released under the BSD 2-Clause license. See `LICENSE` file for 145 | details. 146 | 147 | ## Author 148 | 149 | Logswan is developed by Frederic Cambus. 150 | 151 | - Site: https://www.cambus.net 152 | 153 | ## Resources 154 | 155 | Project homepage: https://www.logswan.org 156 | 157 | GitHub: https://github.com/fcambus/logswan 158 | 159 | [1]: https://openports.pl/path/www/logswan 160 | [2]: https://pkgsrc.se/www/logswan 161 | [3]: https://www.freshports.org/www/logswan 162 | [4]: https://packages.debian.org/search?keywords=logswan 163 | [5]: https://packages.ubuntu.com/search?keywords=logswan 164 | [6]: https://github.com/void-linux/void-packages/tree/master/srcpkgs/logswan 165 | [7]: https://packages.gentoo.org/packages/www-misc/logswan 166 | [8]: https://formulae.brew.sh/formula/logswan 167 | [9]: https://db-ip.com/db/lite.php 168 | [10]: https://dev.maxmind.com/geoip/geoip2/geolite2/ 169 | -------------------------------------------------------------------------------- /THANKS: -------------------------------------------------------------------------------- 1 | - Antti Kiuru for the Logswan ASCII logo 2 | - Ted Unangst and Todd Miller for strtonum.c 3 | - Austin Appleby for the MurmurHash3 hash function 4 | - Artem Zaytsev for the HyperLogLog C library (https://github.com/avz/hll) 5 | -------------------------------------------------------------------------------- /compat/compat.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT_H 2 | #define COMPAT_H 3 | 4 | #ifndef HAVE_PLEDGE 5 | #include "pledge.h" 6 | #endif 7 | 8 | #ifndef HAVE_STRTONUM 9 | #include "strtonum.h" 10 | #endif 11 | 12 | /* Use CLOCK_REALTIME if CLOCK_MONOTONIC is not available */ 13 | #ifndef CLOCK_MONOTONIC 14 | #define CLOCK_MONOTONIC CLOCK_REALTIME 15 | #endif 16 | 17 | #ifndef timespecsub 18 | #define timespecsub(tsp, usp, vsp) \ 19 | do { \ 20 | (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ 21 | (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ 22 | if ((vsp)->tv_nsec < 0) { \ 23 | (vsp)->tv_sec--; \ 24 | (vsp)->tv_nsec += 1000000000L; \ 25 | } \ 26 | } while (0) 27 | #endif 28 | 29 | #endif /* COMPAT_H */ 30 | -------------------------------------------------------------------------------- /compat/pledge.c: -------------------------------------------------------------------------------- 1 | int 2 | pledge(const char *promises, const char *execpromises) 3 | { 4 | (void)promises; 5 | (void)execpromises; 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /compat/pledge.h: -------------------------------------------------------------------------------- 1 | int pledge(const char *, const char *); 2 | -------------------------------------------------------------------------------- /compat/strtonum.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: strtonum.c,v 1.7 2013/04/17 18:40:58 tedu Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2004 Ted Unangst and Todd Miller 5 | * All rights reserved. 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #define INVALID 1 25 | #define TOOSMALL 2 26 | #define TOOLARGE 3 27 | 28 | long long 29 | strtonum(const char *numstr, long long minval, long long maxval, 30 | const char **errstrp) 31 | { 32 | long long ll = 0; 33 | int error = 0; 34 | char *ep; 35 | struct errval { 36 | const char *errstr; 37 | int err; 38 | } ev[4] = { 39 | { NULL, 0 }, 40 | { "invalid", EINVAL }, 41 | { "too small", ERANGE }, 42 | { "too large", ERANGE }, 43 | }; 44 | 45 | ev[0].err = errno; 46 | errno = 0; 47 | if (minval > maxval) { 48 | error = INVALID; 49 | } else { 50 | ll = strtoll(numstr, &ep, 10); 51 | if (numstr == ep || *ep != '\0') 52 | error = INVALID; 53 | else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) 54 | error = TOOSMALL; 55 | else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) 56 | error = TOOLARGE; 57 | } 58 | if (errstrp != NULL) 59 | *errstrp = ev[error].errstr; 60 | errno = ev[error].err; 61 | if (error) 62 | ll = 0; 63 | 64 | return (ll); 65 | } 66 | -------------------------------------------------------------------------------- /compat/strtonum.h: -------------------------------------------------------------------------------- 1 | long long strtonum(const char *, long long, long long, const char **); 2 | -------------------------------------------------------------------------------- /deps/MurmurHash3/MurmurHash3.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | MurmurHash3 was written by Austin Appleby, and is placed in the public 3 | domain. The author hereby disclaims copyright to this source code. 4 | */ 5 | 6 | #include "MurmurHash3.h" 7 | 8 | #define ROTL32(x, r) ((x) << (r)) | ((x) >> (32 - (r))) 9 | 10 | uint32_t MurmurHash3_x86_32(const void *key, uint32_t len, uint32_t seed) { 11 | const uint8_t *data = (const uint8_t *)key; 12 | const int32_t nblocks = (int32_t)len / 4; 13 | 14 | uint32_t h1 = seed; 15 | int i; 16 | 17 | const uint32_t c1 = 0xcc9e2d51; 18 | const uint32_t c2 = 0x1b873593; 19 | const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); 20 | 21 | for(i = -nblocks; i; i++) 22 | { 23 | uint32_t k1 = blocks[i]; 24 | 25 | k1 *= c1; 26 | k1 = ROTL32(k1, 15); 27 | k1 *= c2; 28 | 29 | h1 ^= k1; 30 | h1 = ROTL32(h1, 13); 31 | h1 = h1 * 5 + 0xe6546b64; 32 | } 33 | 34 | const uint8_t * tail = (const uint8_t *)(data + nblocks * 4); 35 | 36 | uint32_t k1 = 0; 37 | 38 | switch(len & 3) 39 | { 40 | case 3: 41 | k1 ^= (uint32_t)tail[2] << 16; /* FALLTHROUGH */ 42 | case 2: 43 | k1 ^= (uint32_t)tail[1] << 8; /* FALLTHROUGH */ 44 | case 1: 45 | k1 ^= tail[0]; 46 | k1 *= c1; 47 | k1 = ROTL32(k1, 15); 48 | k1 *= c2; 49 | h1 ^= k1; 50 | }; 51 | 52 | h1 ^= len; 53 | 54 | h1 ^= h1 >> 16; 55 | h1 *= 0x85ebca6b; 56 | h1 ^= h1 >> 13; 57 | h1 *= 0xc2b2ae35; 58 | h1 ^= h1 >> 16; 59 | 60 | return h1; 61 | } 62 | -------------------------------------------------------------------------------- /deps/MurmurHash3/MurmurHash3.h: -------------------------------------------------------------------------------- 1 | #ifndef _MURMURHASH3_H_ 2 | #define _MURMURHASH3_H_ 3 | 4 | #include 5 | 6 | uint32_t MurmurHash3_x86_32(const void * key, uint32_t len, uint32_t seed); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /deps/hll/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Artem Zaytsev 2 | Permission is hereby granted, free of charge, to any person obtaining a copy 3 | of this software and associated documentation files (the "Software"), to deal 4 | in the Software without restriction, including without limitation the rights 5 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | The above copyright notice and this permission notice shall be included 9 | in all copies or substantial portions of the Software. 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 13 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 14 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 16 | OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /deps/hll/hll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "../MurmurHash3/MurmurHash3.h" 9 | #include "hll.h" 10 | 11 | static __inline uint8_t _hll_rank(uint32_t hash, uint8_t bits) { 12 | uint8_t i; 13 | 14 | for(i = 1; i <= 32 - bits; i++) { 15 | if(hash & 1) 16 | break; 17 | 18 | hash >>= 1; 19 | } 20 | 21 | return i; 22 | } 23 | 24 | int hll_init(struct HLL *hll, uint8_t bits) { 25 | if(bits < 4 || bits > 20) { 26 | errno = ERANGE; 27 | return -1; 28 | } 29 | 30 | hll->bits = bits; 31 | hll->size = (size_t)1 << bits; 32 | hll->registers = calloc(hll->size, 1); 33 | 34 | return 0; 35 | } 36 | 37 | void hll_destroy(struct HLL *hll) { 38 | free(hll->registers); 39 | 40 | hll->registers = NULL; 41 | } 42 | 43 | static __inline void _hll_add_hash(struct HLL *hll, uint32_t hash) { 44 | uint32_t index = hash >> (32 - hll->bits); 45 | uint8_t rank = _hll_rank(hash, hll->bits); 46 | 47 | if(rank > hll->registers[index]) { 48 | hll->registers[index] = rank; 49 | } 50 | } 51 | 52 | void hll_add(struct HLL *hll, const void *buf, size_t size) { 53 | uint32_t hash = MurmurHash3_x86_32((const char *)buf, (uint32_t)size, 0x5f61767a); 54 | 55 | _hll_add_hash(hll, hash); 56 | } 57 | 58 | double hll_count(const struct HLL *hll) { 59 | double alpha_mm; 60 | uint32_t i; 61 | 62 | switch (hll->bits) { 63 | case 4: 64 | alpha_mm = 0.673; 65 | break; 66 | case 5: 67 | alpha_mm = 0.697; 68 | break; 69 | case 6: 70 | alpha_mm = 0.709; 71 | break; 72 | default: 73 | alpha_mm = 0.7213 / (1.0 + 1.079 / (double)hll->size); 74 | break; 75 | } 76 | 77 | alpha_mm *= ((double)hll->size * (double)hll->size); 78 | 79 | double sum = 0; 80 | for(i = 0; i < hll->size; i++) { 81 | sum += 1.0 / (1 << hll->registers[i]); 82 | } 83 | 84 | double estimate = alpha_mm / sum; 85 | 86 | if (estimate <= 5.0 / 2.0 * (double)hll->size) { 87 | int zeros = 0; 88 | 89 | for(i = 0; i < hll->size; i++) 90 | zeros += (hll->registers[i] == 0); 91 | 92 | if(zeros) 93 | estimate = (double)hll->size * log((double)hll->size / zeros); 94 | 95 | } else if (estimate > (1.0 / 30.0) * 4294967296.0) { 96 | estimate = -4294967296.0 * log(1.0 - (estimate / 4294967296.0)); 97 | } 98 | 99 | return estimate; 100 | } 101 | 102 | int hll_merge(struct HLL *dst, const struct HLL *src) { 103 | uint32_t i; 104 | 105 | if(dst->bits != src->bits) { 106 | errno = EINVAL; 107 | return -1; 108 | } 109 | 110 | for(i = 0; i < dst->size; i++) { 111 | if(src->registers[i] > dst->registers[i]) 112 | dst->registers[i] = src->registers[i]; 113 | } 114 | 115 | return 0; 116 | } 117 | 118 | int hll_load(struct HLL *hll, const void *registers, size_t size) { 119 | uint8_t bits = 0; 120 | size_t s = size; 121 | 122 | while(s) { 123 | if(s & 1) 124 | break; 125 | 126 | bits++; 127 | 128 | s >>= 1; 129 | } 130 | 131 | if(!bits || ((size_t)1 << bits) != size) { 132 | errno = EINVAL; 133 | return -1; 134 | } 135 | 136 | if(hll_init(hll, bits) == -1) 137 | return -1; 138 | 139 | memcpy(hll->registers, registers, size); 140 | 141 | return 0; 142 | } 143 | 144 | extern uint32_t _hll_hash(const struct HLL *hll) { 145 | return MurmurHash3_x86_32(hll->registers, (uint32_t)hll->size, 0); 146 | } 147 | -------------------------------------------------------------------------------- /deps/hll/hll.h: -------------------------------------------------------------------------------- 1 | #ifndef AVZ_HLL_H 2 | #define AVZ_HLL_H 3 | 4 | #include 5 | #include 6 | 7 | struct HLL { 8 | uint8_t bits; 9 | 10 | size_t size; 11 | uint8_t *registers; 12 | }; 13 | 14 | extern int hll_init(struct HLL *hll, uint8_t bits); 15 | extern int hll_load(struct HLL *hll, const void *registers, size_t size); 16 | extern void hll_destroy(struct HLL *hll); 17 | extern int hll_merge(struct HLL *dst, const struct HLL *src); 18 | extern void hll_add(struct HLL *hll, const void *buf, size_t size); 19 | extern double hll_count(const struct HLL *hll); 20 | 21 | extern uint32_t _hll_hash(const struct HLL *hll); 22 | 23 | #endif /* AVZ_HLL_H */ 24 | -------------------------------------------------------------------------------- /examples/logswan.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2022-05-06 16:13:26", 3 | "generator": "Logswan 2.1.12", 4 | "file_name": "logswan.log", 5 | "file_size": 10172, 6 | "processed_lines": 68, 7 | "invalid_lines": 0, 8 | "bandwidth": 592748, 9 | "runtime": 0.0077210000000000004, 10 | "hits": { 11 | "ipv4": 34, 12 | "ipv6": 34, 13 | "total": 68 14 | }, 15 | "visits": { 16 | "ipv4": 7, 17 | "ipv6": 7, 18 | "total": 14 19 | }, 20 | "continents": [ 21 | { 22 | "data": "EU", 23 | "name": "Europe", 24 | "hits": 28 25 | }, 26 | { 27 | "data": "NA", 28 | "name": "North America", 29 | "hits": 28 30 | }, 31 | { 32 | "data": "OC", 33 | "name": "Oceania", 34 | "hits": 12 35 | } 36 | ], 37 | "countries": [ 38 | { 39 | "data": "AU", 40 | "name": "Australia", 41 | "hits": 12 42 | }, 43 | { 44 | "data": "DE", 45 | "name": "Germany", 46 | "hits": 2 47 | }, 48 | { 49 | "data": "FR", 50 | "name": "France", 51 | "hits": 26 52 | }, 53 | { 54 | "data": "US", 55 | "name": "United States", 56 | "hits": 28 57 | } 58 | ], 59 | "hours": [ 60 | { 61 | "data": 10, 62 | "hits": 4 63 | }, 64 | { 65 | "data": 11, 66 | "hits": 8 67 | }, 68 | { 69 | "data": 12, 70 | "hits": 26 71 | }, 72 | { 73 | "data": 13, 74 | "hits": 4 75 | }, 76 | { 77 | "data": 14, 78 | "hits": 10 79 | }, 80 | { 81 | "data": 15, 82 | "hits": 2 83 | }, 84 | { 85 | "data": 16, 86 | "hits": 4 87 | }, 88 | { 89 | "data": 18, 90 | "hits": 10 91 | } 92 | ], 93 | "methods": [ 94 | { 95 | "data": "GET", 96 | "hits": 66 97 | }, 98 | { 99 | "data": "HEAD", 100 | "hits": 2 101 | } 102 | ], 103 | "protocols": [ 104 | { 105 | "data": "HTTP/1.1", 106 | "hits": 66 107 | }, 108 | { 109 | "data": "HTTP/1.0", 110 | "hits": 2 111 | } 112 | ], 113 | "status": [ 114 | { 115 | "data": 200, 116 | "hits": 44 117 | }, 118 | { 119 | "data": 404, 120 | "hits": 24 121 | } 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /examples/logswan.log: -------------------------------------------------------------------------------- 1 | 1.1.1.1 - - [09/Dec/2018:10:59:20 +0100] "HEAD / HTTP/1.1" 200 8142 "" "curl/7.62.0" 2 | 1.1.1.1 - - [09/Dec/2018:10:59:26 +0100] "GET / HTTP/1.1" 200 8142 "" "curl/7.62.0" 3 | 1.1.1.1 - - [09/Dec/2018:11:00:02 +0100] "GET /robots.txt HTTP/1.1" 404 0 "" "curl/7.62.0" 4 | 1.1.1.1 - - [09/Dec/2018:11:06:22 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 5 | 1.1.1.1 - - [09/Dec/2018:11:06:22 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 6 | 1.1.1.1 - - [09/Dec/2018:11:06:23 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "https://www.logswan.org/" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 7 | 2.2.2.2 - - [09/Dec/2018:12:36:38 +0100] "GET /files/logswan-1.00.tar.gz HTTP/1.1" 200 14571 "" "curl/7.62.0" 8 | 2.2.2.2 - - [09/Dec/2018:12:36:47 +0100] "GET /files/logswan-1.00.tar.gz HTTP/1.1" 200 14571 "" "Wget/1.19.5 (openbsd6.4)" 9 | 2.2.2.2 - - [09/Dec/2018:12:36:51 +0100] "GET /files/logswan-1.01.tar.gz HTTP/1.1" 200 14790 "" "Wget/1.19.5 (openbsd6.4)" 10 | 2.2.2.2 - - [09/Dec/2018:12:36:53 +0100] "GET /files/logswan-1.02.tar.gz HTTP/1.1" 200 14931 "" "Wget/1.19.5 (openbsd6.4)" 11 | 2.2.2.2 - - [09/Dec/2018:12:36:55 +0100] "GET /files/logswan-1.03.tar.gz HTTP/1.1" 200 16485 "" "Wget/1.19.5 (openbsd6.4)" 12 | 2.2.2.2 - - [09/Dec/2018:12:36:57 +0100] "GET /files/logswan-1.04.tar.gz HTTP/1.1" 200 16913 "" "Wget/1.19.5 (openbsd6.4)" 13 | 2.2.2.2 - - [09/Dec/2018:12:37:00 +0100] "GET /files/logswan-1.05.tar.gz HTTP/1.1" 200 17392 "" "Wget/1.19.5 (openbsd6.4)" 14 | 2.2.2.2 - - [09/Dec/2018:12:37:02 +0100] "GET /files/logswan-1.06.tar.gz HTTP/1.1" 200 17589 "" "Wget/1.19.5 (openbsd6.4)" 15 | 2.2.2.2 - - [09/Dec/2018:12:37:05 +0100] "GET /files/logswan-1.07.tar.gz HTTP/1.1" 200 18697 "" "Wget/1.19.5 (openbsd6.4)" 16 | 2.2.2.2 - - [09/Dec/2018:12:37:13 +0100] "GET /files/logswan-2.0.0.tar.gz HTTP/1.1" 200 20683 "" "Wget/1.19.5 (openbsd6.4)" 17 | 2.2.2.2 - - [09/Dec/2018:12:37:16 +0100] "GET /files/logswan-2.0.1.tar.gz HTTP/1.1" 200 21336 "" "Wget/1.19.5 (openbsd6.4)" 18 | 2.2.2.2 - - [09/Dec/2018:12:37:18 +0100] "GET /files/logswan-2.0.2.tar.gz HTTP/1.1" 200 21367 "" "Wget/1.19.5 (openbsd6.4)" 19 | 2.2.2.2 - - [09/Dec/2018:12:37:21 +0100] "GET /files/logswan-2.0.3.tar.gz HTTP/1.1" 200 21799 "" "Wget/1.19.5 (openbsd6.4)" 20 | 3.3.3.3 - - [09/Dec/2018:13:16:24 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 21 | 3.3.3.3 - - [09/Dec/2018:13:16:24 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 22 | 4.4.4.4 - - [09/Dec/2018:14:32:28 +0100] "GET / HTTP/1.1" 200 8142 "" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 23 | 4.4.4.4 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 24 | 4.4.4.4 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 25 | 4.4.4.4 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 26 | 4.4.4.4 - - [09/Dec/2018:14:32:29 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 27 | 5.5.5.5 - - [09/Dec/2018:15:21:57 +0100] "GET / HTTP/1.0" 200 8142 "" "Lynx/2.8.9rel.1 libwww-FM/2.14 SSL-MM/1.4.1" 28 | 6.6.6.6 - - [09/Dec/2018:16:49:12 +0100] "GET / HTTP/1.1" 200 8142 "https://www.logswan.org/" "Dillo/3.0.5" 29 | 6.6.6.6 - - [09/Dec/2018:16:49:12 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "Dillo/3.0.5" 30 | 7.7.7.7 - - [09/Dec/2018:18:17:29 +0100] "GET / HTTP/1.1" 200 8142 "" "NetSurf/3.8 (OpenBSD)" 31 | 7.7.7.7 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 32 | 7.7.7.7 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 33 | 7.7.7.7 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 34 | 7.7.7.7 - - [09/Dec/2018:18:17:31 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "" "NetSurf/3.8 (OpenBSD)" 35 | ::ffff:101:101 - - [09/Dec/2018:10:59:20 +0100] "HEAD / HTTP/1.1" 200 8142 "" "curl/7.62.0" 36 | ::ffff:101:101 - - [09/Dec/2018:10:59:26 +0100] "GET / HTTP/1.1" 200 8142 "" "curl/7.62.0" 37 | ::ffff:101:101 - - [09/Dec/2018:11:00:02 +0100] "GET /robots.txt HTTP/1.1" 404 0 "" "curl/7.62.0" 38 | ::ffff:101:101 - - [09/Dec/2018:11:06:22 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 39 | ::ffff:101:101 - - [09/Dec/2018:11:06:22 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 40 | ::ffff:101:101 - - [09/Dec/2018:11:06:23 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "https://www.logswan.org/" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 41 | ::ffff:202:202 - - [09/Dec/2018:12:36:38 +0100] "GET /files/logswan-1.00.tar.gz HTTP/1.1" 200 14571 "" "curl/7.62.0" 42 | ::ffff:202:202 - - [09/Dec/2018:12:36:47 +0100] "GET /files/logswan-1.00.tar.gz HTTP/1.1" 200 14571 "" "Wget/1.19.5 (openbsd6.4)" 43 | ::ffff:202:202 - - [09/Dec/2018:12:36:51 +0100] "GET /files/logswan-1.01.tar.gz HTTP/1.1" 200 14790 "" "Wget/1.19.5 (openbsd6.4)" 44 | ::ffff:202:202 - - [09/Dec/2018:12:36:53 +0100] "GET /files/logswan-1.02.tar.gz HTTP/1.1" 200 14931 "" "Wget/1.19.5 (openbsd6.4)" 45 | ::ffff:202:202 - - [09/Dec/2018:12:36:55 +0100] "GET /files/logswan-1.03.tar.gz HTTP/1.1" 200 16485 "" "Wget/1.19.5 (openbsd6.4)" 46 | ::ffff:202:202 - - [09/Dec/2018:12:36:57 +0100] "GET /files/logswan-1.04.tar.gz HTTP/1.1" 200 16913 "" "Wget/1.19.5 (openbsd6.4)" 47 | ::ffff:202:202 - - [09/Dec/2018:12:37:00 +0100] "GET /files/logswan-1.05.tar.gz HTTP/1.1" 200 17392 "" "Wget/1.19.5 (openbsd6.4)" 48 | ::ffff:202:202 - - [09/Dec/2018:12:37:02 +0100] "GET /files/logswan-1.06.tar.gz HTTP/1.1" 200 17589 "" "Wget/1.19.5 (openbsd6.4)" 49 | ::ffff:202:202 - - [09/Dec/2018:12:37:05 +0100] "GET /files/logswan-1.07.tar.gz HTTP/1.1" 200 18697 "" "Wget/1.19.5 (openbsd6.4)" 50 | ::ffff:202:202 - - [09/Dec/2018:12:37:13 +0100] "GET /files/logswan-2.0.0.tar.gz HTTP/1.1" 200 20683 "" "Wget/1.19.5 (openbsd6.4)" 51 | ::ffff:202:202 - - [09/Dec/2018:12:37:16 +0100] "GET /files/logswan-2.0.1.tar.gz HTTP/1.1" 200 21336 "" "Wget/1.19.5 (openbsd6.4)" 52 | ::ffff:202:202 - - [09/Dec/2018:12:37:18 +0100] "GET /files/logswan-2.0.2.tar.gz HTTP/1.1" 200 21367 "" "Wget/1.19.5 (openbsd6.4)" 53 | ::ffff:202:202 - - [09/Dec/2018:12:37:21 +0100] "GET /files/logswan-2.0.3.tar.gz HTTP/1.1" 200 21799 "" "Wget/1.19.5 (openbsd6.4)" 54 | ::ffff:303:303 - - [09/Dec/2018:13:16:24 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 55 | ::ffff:303:303 - - [09/Dec/2018:13:16:24 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 56 | ::ffff:404:404 - - [09/Dec/2018:14:32:28 +0100] "GET / HTTP/1.1" 200 8142 "" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 57 | ::ffff:404:404 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 58 | ::ffff:404:404 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 59 | ::ffff:404:404 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 60 | ::ffff:404:404 - - [09/Dec/2018:14:32:29 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 61 | ::ffff:505:505 - - [09/Dec/2018:15:21:57 +0100] "GET / HTTP/1.0" 200 8142 "" "Lynx/2.8.9rel.1 libwww-FM/2.14 SSL-MM/1.4.1" 62 | ::ffff:606:606 - - [09/Dec/2018:16:49:12 +0100] "GET / HTTP/1.1" 200 8142 "https://www.logswan.org/" "Dillo/3.0.5" 63 | ::ffff:606:606 - - [09/Dec/2018:16:49:12 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "Dillo/3.0.5" 64 | ::ffff:707:707 - - [09/Dec/2018:18:17:29 +0100] "GET / HTTP/1.1" 200 8142 "" "NetSurf/3.8 (OpenBSD)" 65 | ::ffff:707:707 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 66 | ::ffff:707:707 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 67 | ::ffff:707:707 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 68 | ::ffff:707:707 - - [09/Dec/2018:18:17:31 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "" "NetSurf/3.8 (OpenBSD)" 69 | -------------------------------------------------------------------------------- /logswan.spec: -------------------------------------------------------------------------------- 1 | Name: logswan 2 | Version: 2.1.15 3 | Release: 1%{?dist} 4 | Summary: Fast Web log analyzer using probabilistic data structures 5 | 6 | License: BSD-2-Clause 7 | URL: https://github.com/fcambus/logswan 8 | Source0: %{url}/releases/download/%{version}/%{name}-%{version}.tar.gz 9 | 10 | BuildRequires: jansson-devel 11 | BuildRequires: libmaxminddb-devel 12 | BuildRequires: cmake 13 | BuildRequires: gcc 14 | 15 | %description 16 | Logswan is a fast Web log analyzer using probabilistic data structures. It is 17 | targeted at very large log files, typically APIs logs. It has constant memory 18 | usage regardless of the log file size, and takes approximatively 4MB of RAM. 19 | 20 | Unique visitors counting is performed using two HyperLogLog counters (one for 21 | IPv4, and another one for IPv6), providing a relative accuracy of 0.10%. 22 | 23 | Project design goals include : speed, memory-usage efficiency, and keeping the 24 | code as simple as possible. 25 | 26 | %prep 27 | %autosetup 28 | 29 | %build 30 | %cmake . 31 | %cmake_build 32 | 33 | %install 34 | %cmake_install 35 | 36 | %check 37 | 38 | %files 39 | %{_bindir}/%{name} 40 | %{_mandir}/man1/%{name}.1* 41 | %license LICENSE 42 | %doc README.md 43 | -------------------------------------------------------------------------------- /man/logswan.1: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright (c) 2015-2025, Frederic Cambus 3 | .\" All rights reserved. 4 | .\" 5 | .\" Redistribution and use in source and binary forms, with or without 6 | .\" modification, are permitted provided that the following conditions are met: 7 | .\" 8 | .\" * Redistributions of source code must retain the above copyright 9 | .\" notice, this list of conditions and the following disclaimer. 10 | .\" 11 | .\" * Redistributions in binary form must reproduce the above copyright 12 | .\" notice, this list of conditions and the following disclaimer in the 13 | .\" documentation and/or other materials provided with the distribution. 14 | .\" 15 | .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | .\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | .\" ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 19 | .\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | .\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | .\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | .\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | .\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | .\" POSSIBILITY OF SUCH DAMAGE. 26 | .\" 27 | .Dd $Mdocdate: March 22 2025 $ 28 | .Dt LOGSWAN 1 29 | .Os 30 | .Sh NAME 31 | .Nm logswan 32 | .Nd fast Web log analyzer using probabilistic data structures 33 | .Sh SYNOPSIS 34 | .Nm 35 | .Op Fl ghv 36 | .Op Fl d Ar db 37 | .Ar logfile 38 | .Sh DESCRIPTION 39 | .Nm 40 | is a fast Web log analyzer using probabilistic data structures. 41 | It is targeted at very large log files, typically APIs logs. 42 | It has constant memory usage regardless of the log file size, and takes 43 | approximatively 4MB of RAM. 44 | .Pp 45 | Unique visitors counting is performed using two HyperLogLog counters (one for 46 | IPv4, and another one for IPv6), providing a relative accuracy of 0.10%. 47 | .Pp 48 | If 49 | .Ar file 50 | is a single dash (`-'), 51 | .Nm 52 | reads from the standard input. 53 | .Pp 54 | The options are as follows: 55 | .Bl -tag -width Ds 56 | .It Fl d Ar db 57 | Specify path to a GeoIP database. 58 | .It Fl g 59 | Enable GeoIP lookups. 60 | .It Fl h 61 | Display usage. 62 | .It Fl v 63 | Display version. 64 | .El 65 | .Sh EXAMPLES 66 | The following script can be used to process all log files in the current 67 | directory and save the output in a file: 68 | .Bd -literal -offset indent 69 | #!/bin/sh 70 | for file in $(ls *.log) 71 | do 72 | logswan $file > $file.json 73 | done 74 | exit 0 75 | .Ed 76 | .Sh AUTHORS 77 | .Nm 78 | was written by 79 | .An Frederic Cambus . 80 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-02-15 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | const char *methods_names[] = { 16 | "GET", 17 | "POST", 18 | "HEAD", 19 | "OPTIONS", 20 | "PUT", 21 | "DELETE", 22 | "TRACE", 23 | "CONNECT", 24 | "PATCH" 25 | }; 26 | 27 | const char *protocols_names[] = { 28 | "HTTP/1.1", 29 | "HTTP/1.0", 30 | "HTTP/2.0", 31 | "HTTP/3" 32 | }; 33 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-11-16 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | #ifndef CONFIG_H 16 | #define CONFIG_H 17 | 18 | #define VERSION "Logswan 2.1.15" 19 | 20 | enum { 21 | HLL_BITS = 20, 22 | STATUS_CODE_MAX = 512, 23 | 24 | CONTINENTS = 7, 25 | COUNTRIES = 250, 26 | METHODS = 9, 27 | PROTOCOLS = 4 28 | }; 29 | 30 | extern char *methods_names[]; 31 | extern char *protocols_names[]; 32 | 33 | #endif /* CONFIG_H */ 34 | -------------------------------------------------------------------------------- /src/continents.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-02-15 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | const char *continents_id[] = { 16 | "AF", 17 | "AN", 18 | "AS", 19 | "EU", 20 | "NA", 21 | "OC", 22 | "SA" 23 | }; 24 | 25 | const char *continents_names[] = { 26 | "Africa", 27 | "Antarctica", 28 | "Asia", 29 | "Europe", 30 | "North America", 31 | "Oceania", 32 | "South America" 33 | }; 34 | -------------------------------------------------------------------------------- /src/continents.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-02-15 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | #ifndef CONTINENTS_H 16 | #define CONTINENTS_H 17 | 18 | extern char *continents_id[]; 19 | extern char *continents_names[]; 20 | 21 | #endif /* CONTINENTS */ 22 | -------------------------------------------------------------------------------- /src/countries.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-02-15 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | const char *countries_id[] = { 16 | "AD", 17 | "AE", 18 | "AF", 19 | "AG", 20 | "AI", 21 | "AL", 22 | "AM", 23 | "AO", 24 | "AQ", 25 | "AR", 26 | "AS", 27 | "AT", 28 | "AU", 29 | "AW", 30 | "AX", 31 | "AZ", 32 | "BA", 33 | "BB", 34 | "BD", 35 | "BE", 36 | "BF", 37 | "BG", 38 | "BH", 39 | "BI", 40 | "BJ", 41 | "BL", 42 | "BM", 43 | "BN", 44 | "BO", 45 | "BQ", 46 | "BR", 47 | "BS", 48 | "BT", 49 | "BV", 50 | "BW", 51 | "BY", 52 | "BZ", 53 | "CA", 54 | "CC", 55 | "CD", 56 | "CF", 57 | "CG", 58 | "CH", 59 | "CI", 60 | "CK", 61 | "CL", 62 | "CM", 63 | "CN", 64 | "CO", 65 | "CR", 66 | "CU", 67 | "CV", 68 | "CW", 69 | "CX", 70 | "CY", 71 | "CZ", 72 | "DE", 73 | "DJ", 74 | "DK", 75 | "DM", 76 | "DO", 77 | "DZ", 78 | "EC", 79 | "EE", 80 | "EG", 81 | "EH", 82 | "ER", 83 | "ES", 84 | "ET", 85 | "FI", 86 | "FJ", 87 | "FK", 88 | "FM", 89 | "FO", 90 | "FR", 91 | "GA", 92 | "GB", 93 | "GD", 94 | "GE", 95 | "GF", 96 | "GG", 97 | "GH", 98 | "GI", 99 | "GL", 100 | "GM", 101 | "GN", 102 | "GP", 103 | "GQ", 104 | "GR", 105 | "GS", 106 | "GT", 107 | "GU", 108 | "GW", 109 | "GY", 110 | "HK", 111 | "HM", 112 | "HN", 113 | "HR", 114 | "HT", 115 | "HU", 116 | "ID", 117 | "IE", 118 | "IL", 119 | "IM", 120 | "IN", 121 | "IO", 122 | "IQ", 123 | "IR", 124 | "IS", 125 | "IT", 126 | "JE", 127 | "JM", 128 | "JO", 129 | "JP", 130 | "KE", 131 | "KG", 132 | "KH", 133 | "KI", 134 | "KM", 135 | "KN", 136 | "KP", 137 | "KR", 138 | "KW", 139 | "KY", 140 | "KZ", 141 | "LA", 142 | "LB", 143 | "LC", 144 | "LI", 145 | "LK", 146 | "LR", 147 | "LS", 148 | "LT", 149 | "LU", 150 | "LV", 151 | "LY", 152 | "MA", 153 | "MC", 154 | "MD", 155 | "ME", 156 | "MF", 157 | "MG", 158 | "MH", 159 | "MK", 160 | "ML", 161 | "MM", 162 | "MN", 163 | "MO", 164 | "MP", 165 | "MQ", 166 | "MR", 167 | "MS", 168 | "MT", 169 | "MU", 170 | "MV", 171 | "MW", 172 | "MX", 173 | "MY", 174 | "MZ", 175 | "NA", 176 | "NC", 177 | "NE", 178 | "NF", 179 | "NG", 180 | "NI", 181 | "NL", 182 | "NO", 183 | "NP", 184 | "NR", 185 | "NU", 186 | "NZ", 187 | "OM", 188 | "PA", 189 | "PE", 190 | "PF", 191 | "PG", 192 | "PH", 193 | "PK", 194 | "PL", 195 | "PM", 196 | "PN", 197 | "PR", 198 | "PS", 199 | "PT", 200 | "PW", 201 | "PY", 202 | "QA", 203 | "RE", 204 | "RO", 205 | "RS", 206 | "RU", 207 | "RW", 208 | "SA", 209 | "SB", 210 | "SC", 211 | "SD", 212 | "SE", 213 | "SG", 214 | "SH", 215 | "SI", 216 | "SJ", 217 | "SK", 218 | "SL", 219 | "SM", 220 | "SN", 221 | "SO", 222 | "SR", 223 | "SS", 224 | "ST", 225 | "SV", 226 | "SX", 227 | "SY", 228 | "SZ", 229 | "TC", 230 | "TD", 231 | "TF", 232 | "TG", 233 | "TH", 234 | "TJ", 235 | "TK", 236 | "TL", 237 | "TM", 238 | "TN", 239 | "TO", 240 | "TR", 241 | "TT", 242 | "TV", 243 | "TW", 244 | "TZ", 245 | "UA", 246 | "UG", 247 | "UM", 248 | "US", 249 | "UY", 250 | "UZ", 251 | "VA", 252 | "VC", 253 | "VE", 254 | "VG", 255 | "VI", 256 | "VN", 257 | "VU", 258 | "WF", 259 | "WS", 260 | "XK", 261 | "YE", 262 | "YT", 263 | "ZA", 264 | "ZM", 265 | "ZW" 266 | }; 267 | 268 | const char *countries_names[] = { 269 | "Andorra", 270 | "United Arab Emirates", 271 | "Afghanistan", 272 | "Antigua and Barbuda", 273 | "Anguilla", 274 | "Albania", 275 | "Armenia", 276 | "Angola", 277 | "Antarctica", 278 | "Argentina", 279 | "American Samoa", 280 | "Austria", 281 | "Australia", 282 | "Aruba", 283 | "Aland Islands", 284 | "Azerbaijan", 285 | "Bosnia and Herzegovina", 286 | "Barbados", 287 | "Bangladesh", 288 | "Belgium", 289 | "Burkina Faso", 290 | "Bulgaria", 291 | "Bahrain", 292 | "Burundi", 293 | "Benin", 294 | "Saint Barthelemy", 295 | "Bermuda", 296 | "Brunei", 297 | "Bolivia", 298 | "Bonaire", 299 | "Brazil", 300 | "Bahamas", 301 | "Bhutan", 302 | "Bouvet Island", 303 | "Botswana", 304 | "Belarus", 305 | "Belize", 306 | "Canada", 307 | "Cocos (Keeling) Islands", 308 | "Democratic Republic of the Congo", 309 | "Central African Republic", 310 | "Republic of the Congo", 311 | "Switzerland", 312 | "Ivory Coast", 313 | "Cook Islands", 314 | "Chile", 315 | "Cameroon", 316 | "China", 317 | "Colombia", 318 | "Costa Rica", 319 | "Cuba", 320 | "Cape Verde", 321 | "Curacao", 322 | "Christmas Island", 323 | "Cyprus", 324 | "Czech Republic", 325 | "Germany", 326 | "Djibouti", 327 | "Denmark", 328 | "Dominica", 329 | "Dominican Republic", 330 | "Algeria", 331 | "Ecuador", 332 | "Estonia", 333 | "Egypt", 334 | "Western Sahara", 335 | "Eritrea", 336 | "Spain", 337 | "Ethiopia", 338 | "Finland", 339 | "Fiji", 340 | "Falkland Islands", 341 | "Micronesia", 342 | "Faroe Islands", 343 | "France", 344 | "Gabon", 345 | "United Kingdom", 346 | "Grenada", 347 | "Georgia", 348 | "French Guiana", 349 | "Guernsey", 350 | "Ghana", 351 | "Gibraltar", 352 | "Greenland", 353 | "Gambia", 354 | "Guinea", 355 | "Guadeloupe", 356 | "Equatorial Guinea", 357 | "Greece", 358 | "South Georgia and the South Sandwich Islands", 359 | "Guatemala", 360 | "Guam", 361 | "Guinea-Bissau", 362 | "Guyana", 363 | "Hong Kong", 364 | "Heard Island and McDonald Islands", 365 | "Honduras", 366 | "Croatia", 367 | "Haiti", 368 | "Hungary", 369 | "Indonesia", 370 | "Ireland", 371 | "Israel", 372 | "Isle of Man", 373 | "India", 374 | "British Indian Ocean Territory", 375 | "Iraq", 376 | "Iran", 377 | "Iceland", 378 | "Italy", 379 | "Jersey", 380 | "Jamaica", 381 | "Jordan", 382 | "Japan", 383 | "Kenya", 384 | "Kyrgyzstan", 385 | "Cambodia", 386 | "Kiribati", 387 | "Comoros", 388 | "Saint Kitts and Nevis", 389 | "North Korea", 390 | "South Korea", 391 | "Kuwait", 392 | "Cayman Islands", 393 | "Kazakhstan", 394 | "Laos", 395 | "Lebanon", 396 | "Saint Lucia", 397 | "Liechtenstein", 398 | "Sri Lanka", 399 | "Liberia", 400 | "Lesotho", 401 | "Lithuania", 402 | "Luxembourg", 403 | "Latvia", 404 | "Libya", 405 | "Morocco", 406 | "Monaco", 407 | "Moldova", 408 | "Montenegro", 409 | "Saint Martin", 410 | "Madagascar", 411 | "Marshall Islands", 412 | "North Macedonia", 413 | "Mali", 414 | "Myanmar", 415 | "Mongolia", 416 | "Macao", 417 | "Northern Mariana Islands", 418 | "Martinique", 419 | "Mauritania", 420 | "Montserrat", 421 | "Malta", 422 | "Mauritius", 423 | "Maldives", 424 | "Malawi", 425 | "Mexico", 426 | "Malaysia", 427 | "Mozambique", 428 | "Namibia", 429 | "New Caledonia", 430 | "Niger", 431 | "Norfolk Island", 432 | "Nigeria", 433 | "Nicaragua", 434 | "Netherlands", 435 | "Norway", 436 | "Nepal", 437 | "Nauru", 438 | "Niue", 439 | "New Zealand", 440 | "Oman", 441 | "Panama", 442 | "Peru", 443 | "French Polynesia", 444 | "Papua New Guinea", 445 | "Philippines", 446 | "Pakistan", 447 | "Poland", 448 | "Saint Pierre and Miquelon", 449 | "Pitcairn Islands", 450 | "Puerto Rico", 451 | "Palestine", 452 | "Portugal", 453 | "Palau", 454 | "Paraguay", 455 | "Qatar", 456 | "Reunion", 457 | "Romania", 458 | "Serbia", 459 | "Russia", 460 | "Rwanda", 461 | "Saudi Arabia", 462 | "Solomon Islands", 463 | "Seychelles", 464 | "Sudan", 465 | "Sweden", 466 | "Singapore", 467 | "Saint Helena", 468 | "Slovenia", 469 | "Svalbard and Jan Mayen", 470 | "Slovakia", 471 | "Sierra Leone", 472 | "San Marino", 473 | "Senegal", 474 | "Somalia", 475 | "Suriname", 476 | "South Sudan", 477 | "Sao Tome and Principe", 478 | "El Salvador", 479 | "Sint Maarten", 480 | "Syria", 481 | "Eswatini", 482 | "Turks and Caicos Islands", 483 | "Chad", 484 | "French Southern Territories", 485 | "Togo", 486 | "Thailand", 487 | "Tajikistan", 488 | "Tokelau", 489 | "East Timor", 490 | "Turkmenistan", 491 | "Tunisia", 492 | "Tonga", 493 | "Turkey", 494 | "Trinidad and Tobago", 495 | "Tuvalu", 496 | "Taiwan", 497 | "Tanzania", 498 | "Ukraine", 499 | "Uganda", 500 | "U.S. Minor Outlying Islands", 501 | "United States", 502 | "Uruguay", 503 | "Uzbekistan", 504 | "Vatican", 505 | "Saint Vincent and the Grenadines", 506 | "Venezuela", 507 | "British Virgin Islands", 508 | "U.S. Virgin Islands", 509 | "Vietnam", 510 | "Vanuatu", 511 | "Wallis and Futuna", 512 | "Samoa", 513 | "Kosovo", 514 | "Yemen", 515 | "Mayotte", 516 | "South Africa", 517 | "Zambia", 518 | "Zimbabwe" 519 | }; 520 | -------------------------------------------------------------------------------- /src/countries.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-02-15 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | #ifndef COUNTRIES_H 16 | #define COUNTRIES_H 17 | 18 | extern char *countries_id[]; 19 | extern char *countries_names[]; 20 | 21 | #endif /* COUNTRIES */ 22 | 23 | -------------------------------------------------------------------------------- /src/logswan.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2023-03-13 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifdef HAVE_SECCOMP 31 | #include 32 | #include 33 | #include "seccomp.h" 34 | #endif 35 | 36 | #include 37 | 38 | #include "compat.h" 39 | #include "config.h" 40 | #include "continents.h" 41 | #include "countries.h" 42 | #include "hll.h" 43 | #include "output.h" 44 | #include "parse.h" 45 | 46 | static void 47 | usage(void) 48 | { 49 | printf("logswan [-ghv] [-d db] logfile\n\n" 50 | "The options are as follows:\n\n" 51 | " -d db Specify path to a GeoIP database.\n" 52 | " -g Enable GeoIP lookups.\n" 53 | " -h Display usage.\n" 54 | " -v Display version.\n"); 55 | } 56 | 57 | int 58 | main(int argc, char *argv[]) 59 | { 60 | struct timespec begin, end, elapsed; 61 | struct HLL unique_ipv4, unique_ipv6; 62 | struct results results; 63 | struct date parsed_date; 64 | struct logline parsed_line; 65 | struct request parsed_request; 66 | struct stat logfile_stat; 67 | 68 | struct sockaddr_in ipv4; 69 | struct sockaddr_in6 ipv6; 70 | 71 | uint64_t bandwidth; 72 | uint32_t status_code; 73 | uint32_t hour; 74 | int gai_error, mmdb_error; 75 | int opt; 76 | 77 | const char *errstr; 78 | char *linebuffer = NULL; 79 | size_t linesize = 0; 80 | char *input; 81 | char *db = NULL; 82 | 83 | bool geoip = false; 84 | bool is_ipv4, is_ipv6; 85 | 86 | MMDB_s geoip2; 87 | MMDB_lookup_result_s lookup; 88 | 89 | FILE *logfile; 90 | 91 | if (pledge("stdio rpath", NULL) == -1) { 92 | err(EXIT_FAILURE, "pledge"); 93 | } 94 | 95 | #ifdef HAVE_SECCOMP 96 | if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { 97 | perror("Can't initialize seccomp"); 98 | return EXIT_FAILURE; 99 | } 100 | 101 | if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &logswan)) { 102 | perror("Can't load seccomp filter"); 103 | return EXIT_FAILURE; 104 | } 105 | #endif 106 | 107 | while ((opt = getopt(argc, argv, "d:ghv")) != -1) { 108 | switch (opt) { 109 | case 'd': 110 | db = optarg; 111 | break; 112 | 113 | case 'g': 114 | geoip = true; 115 | break; 116 | 117 | case 'h': 118 | usage(); 119 | return EXIT_SUCCESS; 120 | 121 | case 'v': 122 | printf("%s\n", VERSION); 123 | return EXIT_SUCCESS; 124 | } 125 | } 126 | 127 | if (optind < argc) { 128 | input = argv[optind]; 129 | } else { 130 | usage(); 131 | return EXIT_SUCCESS; 132 | } 133 | 134 | hll_init(&unique_ipv4, HLL_BITS); 135 | hll_init(&unique_ipv6, HLL_BITS); 136 | 137 | /* Starting timer */ 138 | clock_gettime(CLOCK_MONOTONIC, &begin); 139 | 140 | /* Initializing GeoIP */ 141 | if (geoip) { 142 | if (!db) 143 | db = GEOIP2DIR GEOIP2DB; 144 | 145 | if (MMDB_open(db, MMDB_MODE_MMAP, &geoip2) != MMDB_SUCCESS) 146 | err(EXIT_FAILURE, "Can't open database (%s)", db); 147 | } 148 | 149 | /* Open log file */ 150 | if (!strcmp(input, "-")) { 151 | /* Read from standard input */ 152 | logfile = stdin; 153 | } else { 154 | /* Attempt to read from file */ 155 | if (!(logfile = fopen(input, "r"))) { 156 | perror("Can't open log file"); 157 | return EXIT_FAILURE; 158 | } 159 | } 160 | 161 | /* Get log file size */ 162 | if (fstat(fileno(logfile), &logfile_stat)) { 163 | perror("Can't stat log file"); 164 | return EXIT_FAILURE; 165 | } 166 | 167 | memset(&results, 0, sizeof(struct results)); 168 | results.file_name = input; 169 | results.file_size = logfile_stat.st_size; 170 | 171 | while (getline(&linebuffer, &linesize, logfile) != -1) { 172 | /* Parse and tokenize line */ 173 | parse_line(&parsed_line, linebuffer); 174 | 175 | /* Detect if remote host is IPv4 or IPv6 */ 176 | if (parsed_line.remote_host) { /* Do not feed NULL tokens to inet_pton */ 177 | if ((is_ipv4 = inet_pton(AF_INET, parsed_line.remote_host, &ipv4.sin_addr))) { 178 | is_ipv6 = false; 179 | } else { 180 | is_ipv6 = inet_pton(AF_INET6, parsed_line.remote_host, &ipv6.sin6_addr); 181 | 182 | if (!is_ipv6) { 183 | results.invalid_lines++; 184 | continue; 185 | } 186 | } 187 | } else { 188 | /* Invalid line */ 189 | results.invalid_lines++; 190 | continue; 191 | } 192 | 193 | if (is_ipv4) { 194 | /* Increment hits counter */ 195 | results.hits_ipv4++; 196 | 197 | /* Unique visitors */ 198 | hll_add(&unique_ipv4, parsed_line.remote_host, strlen(parsed_line.remote_host)); 199 | } 200 | 201 | if (is_ipv6) { 202 | /* Increment hits counter */ 203 | results.hits_ipv6++; 204 | 205 | /* Unique visitors */ 206 | hll_add(&unique_ipv6, parsed_line.remote_host, strlen(parsed_line.remote_host)); 207 | } 208 | 209 | if (geoip) { 210 | MMDB_entry_data_s entry_data; 211 | memset(&entry_data, 0, sizeof(MMDB_entry_data_s)); 212 | 213 | lookup = MMDB_lookup_string(&geoip2, parsed_line.remote_host, &gai_error, &mmdb_error); 214 | 215 | MMDB_get_value(&lookup.entry, &entry_data, "country", "iso_code", NULL); 216 | 217 | if (entry_data.has_data) { 218 | /* Increment countries array */ 219 | for (size_t loop = 0; loop < COUNTRIES; loop++) { 220 | if (!strncmp(countries_id[loop], entry_data.utf8_string, 2)) { 221 | results.countries[loop]++; 222 | break; 223 | } 224 | } 225 | } 226 | 227 | MMDB_get_value(&lookup.entry, &entry_data, "continent", "code", NULL); 228 | 229 | if (entry_data.has_data) { 230 | /* Increment continents array */ 231 | for (size_t loop = 0; loop < CONTINENTS; loop++) { 232 | if (!strncmp(continents_id[loop], entry_data.utf8_string, 2)) { 233 | results.continents[loop]++; 234 | break; 235 | } 236 | } 237 | } 238 | } 239 | 240 | /* Hourly distribution */ 241 | if (parsed_line.date) { 242 | parse_date(&parsed_date, parsed_line.date); 243 | 244 | if (parsed_date.hour) { 245 | hour = strtonum(parsed_date.hour, 0, 23, &errstr); 246 | 247 | if (!errstr) { 248 | results.hours[hour]++; 249 | } 250 | } 251 | } 252 | 253 | /* Parse request */ 254 | if (parsed_line.request) { 255 | parse_request(&parsed_request, parsed_line.request); 256 | 257 | if (parsed_request.method) { 258 | for (size_t loop = 0; loop < METHODS; loop++) { 259 | if (!strcmp(methods_names[loop], parsed_request.method)) { 260 | results.methods[loop]++; 261 | break; 262 | } 263 | } 264 | } 265 | 266 | if (parsed_request.protocol) { 267 | for (size_t loop = 0; loop < PROTOCOLS; loop++) { 268 | if (!strcmp(protocols_names[loop], parsed_request.protocol)) { 269 | results.protocols[loop]++; 270 | break; 271 | } 272 | } 273 | } 274 | } 275 | 276 | /* Count HTTP status codes occurrences */ 277 | if (parsed_line.status_code) { 278 | status_code = strtonum(parsed_line.status_code, 0, STATUS_CODE_MAX-1, &errstr); 279 | 280 | if (!errstr) { 281 | results.status[status_code]++; 282 | } 283 | } 284 | 285 | /* Increment bandwidth usage */ 286 | if (parsed_line.object_size) { 287 | bandwidth = strtonum(parsed_line.object_size, 0, INT64_MAX, &errstr); 288 | 289 | if (!errstr) { 290 | results.bandwidth += bandwidth; 291 | } 292 | } 293 | } 294 | 295 | /* Counting hits and processed lines */ 296 | results.hits = results.hits_ipv4 + results.hits_ipv6; 297 | results.processed_lines = results.hits + results.invalid_lines; 298 | 299 | /* Counting unique visitors */ 300 | results.visits_ipv4 = hll_count(&unique_ipv4); 301 | results.visits_ipv6 = hll_count(&unique_ipv6); 302 | results.visits = results.visits_ipv4 + results.visits_ipv6; 303 | 304 | /* Stopping timer */ 305 | clock_gettime(CLOCK_MONOTONIC, &end); 306 | 307 | timespecsub(&end, &begin, &elapsed); 308 | results.runtime = elapsed.tv_sec + elapsed.tv_nsec / 1E9; 309 | 310 | /* Generate timestamp */ 311 | time_t now = time(NULL); 312 | strftime(results.timestamp, 20, "%Y-%m-%d %H:%M:%S", localtime(&now)); 313 | 314 | /* Printing results */ 315 | fprintf(stdout, "%s\n", output(&results)); 316 | fprintf(stderr, "Processed %" PRIu64 " lines in %f seconds.\n", results.processed_lines, results.runtime); 317 | 318 | /* Clean up */ 319 | free(linebuffer); 320 | fclose(logfile); 321 | 322 | if (geoip) 323 | MMDB_close(&geoip2); 324 | 325 | hll_destroy(&unique_ipv4); 326 | hll_destroy(&unique_ipv6); 327 | 328 | return EXIT_SUCCESS; 329 | } 330 | -------------------------------------------------------------------------------- /src/output.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-02-15 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include "config.h" 21 | #include "continents.h" 22 | #include "countries.h" 23 | #include "output.h" 24 | 25 | char 26 | *output(struct results *results) 27 | { 28 | json_t *output = json_object(); 29 | json_t *hits = json_object(); 30 | json_t *visits = json_object(); 31 | json_t *continents = json_array(); 32 | json_t *countries = json_array(); 33 | json_t *hours = json_array(); 34 | json_t *status = json_array(); 35 | json_t *methods = json_array(); 36 | json_t *protocols = json_array(); 37 | 38 | for (size_t loop = 0; loop < CONTINENTS; loop++) { 39 | if (results->continents[loop]) { 40 | json_array_append_new( 41 | continents, 42 | json_pack("{s:s, s:s, s:i}", 43 | "data", continents_id[loop], 44 | "name", continents_names[loop], 45 | "hits", results->continents[loop])); 46 | } 47 | } 48 | 49 | for (size_t loop = 0; loop < COUNTRIES; loop++) { 50 | if (results->countries[loop]) { 51 | json_array_append_new( 52 | countries, 53 | json_pack("{s:s, s:s, s:i}", 54 | "data", countries_id[loop], 55 | "name", countries_names[loop], 56 | "hits", results->countries[loop])); 57 | } 58 | } 59 | 60 | for (size_t loop = 0; loop < 24; loop++) { 61 | if (results->hours[loop]) { 62 | json_array_append_new( 63 | hours, 64 | json_pack("{s:i, s:i}", 65 | "data", loop, 66 | "hits", results->hours[loop])); 67 | } 68 | } 69 | 70 | for (size_t loop = 0; loop < STATUS_CODE_MAX; loop++) { 71 | if (results->status[loop]) { 72 | json_array_append_new( 73 | status, 74 | json_pack("{s:i, s:i}", 75 | "data", loop, 76 | "hits", results->status[loop])); 77 | } 78 | } 79 | 80 | for (size_t loop = 0; loop < METHODS; loop++) { 81 | if (results->methods[loop]) { 82 | json_array_append_new( 83 | methods, 84 | json_pack("{s:s, s:i}", 85 | "data", methods_names[loop], 86 | "hits", results->methods[loop])); 87 | } 88 | } 89 | 90 | for (size_t loop = 0; loop < PROTOCOLS; loop++) { 91 | if (results->protocols[loop]) { 92 | json_array_append_new( 93 | protocols, 94 | json_pack("{s:s, s:i}", 95 | "data", protocols_names[loop], 96 | "hits", results->protocols[loop])); 97 | } 98 | } 99 | 100 | json_object_set_new(hits, "ipv4", json_integer(results->hits_ipv4)); 101 | json_object_set_new(hits, "ipv6", json_integer(results->hits_ipv6)); 102 | json_object_set_new(hits, "total", json_integer(results->hits)); 103 | 104 | json_object_set_new(visits, "ipv4", json_integer(results->visits_ipv4)); 105 | json_object_set_new(visits, "ipv6", json_integer(results->visits_ipv6)); 106 | json_object_set_new(visits, "total", json_integer(results->visits)); 107 | 108 | json_object_set_new(output, "date", json_string(results->timestamp)); 109 | json_object_set_new(output, "generator", json_string(VERSION)); 110 | json_object_set_new(output, "file_name", json_string(results->file_name)); 111 | json_object_set_new(output, "file_size", json_integer(results->file_size)); 112 | json_object_set_new(output, "processed_lines", json_integer(results->processed_lines)); 113 | json_object_set_new(output, "invalid_lines", json_integer(results->invalid_lines)); 114 | json_object_set_new(output, "bandwidth", json_integer(results->bandwidth)); 115 | json_object_set_new(output, "runtime", json_real(results->runtime)); 116 | json_object_set_new(output, "hits", hits); 117 | json_object_set_new(output, "visits", visits); 118 | json_object_set_new(output, "continents", continents); 119 | json_object_set_new(output, "countries", countries); 120 | json_object_set_new(output, "hours", hours); 121 | json_object_set_new(output, "methods", methods); 122 | json_object_set_new(output, "protocols", protocols); 123 | json_object_set_new(output, "status", status); 124 | 125 | return json_dumps(output, JSON_INDENT(3) | JSON_PRESERVE_ORDER); 126 | } 127 | -------------------------------------------------------------------------------- /src/output.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-02-15 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | #ifndef OUTPUT_H 16 | #define OUTPUT_H 17 | 18 | #include 19 | #include 20 | 21 | #include "config.h" 22 | 23 | struct results { 24 | char *file_name; 25 | off_t file_size; 26 | uint64_t invalid_lines; 27 | uint64_t processed_lines; 28 | uint64_t bandwidth; 29 | uint64_t hits; 30 | uint64_t hits_ipv4; 31 | uint64_t hits_ipv6; 32 | uint64_t visits; 33 | uint64_t visits_ipv4; 34 | uint64_t visits_ipv6; 35 | uint64_t continents[CONTINENTS]; 36 | uint64_t countries[COUNTRIES]; 37 | uint64_t hours[24]; 38 | uint64_t methods[METHODS]; 39 | uint64_t protocols[PROTOCOLS]; 40 | uint64_t status[STATUS_CODE_MAX]; 41 | double runtime; 42 | char timestamp[20]; 43 | }; 44 | 45 | char *output(struct results *); 46 | 47 | #endif /* OUTPUT_H */ 48 | -------------------------------------------------------------------------------- /src/parse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-12-02 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | #include 16 | 17 | #include "parse.h" 18 | 19 | void 20 | parse_date(struct date *parsed_date, char *date) 21 | { 22 | parsed_date->day = strtok(date, "/"); 23 | parsed_date->month = strtok(NULL, "/"); 24 | parsed_date->year = strtok(NULL, ":"); 25 | parsed_date->hour = strtok(NULL, ":"); 26 | parsed_date->minute = strtok(NULL, ":"); 27 | parsed_date->second = strtok(NULL, " "); 28 | } 29 | 30 | void 31 | parse_line(struct logline *parsed_line, char *linebuffer) 32 | { 33 | if (*linebuffer) { 34 | /* Remote host */ 35 | parsed_line->remote_host = strtok(linebuffer, " "); 36 | 37 | /* User-identifier */ 38 | strtok(NULL, " "); 39 | 40 | /* User ID */ 41 | strtok(NULL, "["); 42 | 43 | /* Date */ 44 | parsed_line->date = strtok(NULL, "]"); 45 | 46 | /* Requested resource */ 47 | strtok(NULL, "\""); 48 | parsed_line->request = strtok(NULL, "\""); 49 | 50 | /* HTTP status codes */ 51 | parsed_line->status_code = strtok(NULL, " "); 52 | 53 | /* Returned object size */ 54 | parsed_line->object_size = strtok(NULL, " \""); 55 | } 56 | } 57 | 58 | void 59 | parse_request(struct request *parsed_request, char *request) 60 | { 61 | char *pch = strrchr(request, ' '); 62 | 63 | memset(parsed_request, 0, sizeof(*parsed_request)); 64 | 65 | if (pch) { 66 | parsed_request->protocol = pch + 1; 67 | parsed_request->method = strtok(request, " "); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/parse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2021-02-15 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | #ifndef PARSE_H 16 | #define PARSE_H 17 | 18 | struct date { 19 | char *day; 20 | char *month; 21 | char *year; 22 | char *hour; 23 | char *minute; 24 | char *second; 25 | }; 26 | 27 | struct logline { 28 | char *remote_host; 29 | char *date; 30 | char *request; 31 | char *status_code; 32 | char *object_size; 33 | }; 34 | 35 | struct request { 36 | char *method; 37 | char *resource; 38 | char *protocol; 39 | }; 40 | 41 | void parse_date(struct date *, char *); 42 | void parse_line(struct logline *, char *); 43 | void parse_request(struct request *, char *); 44 | 45 | #endif /* PARSE_H */ 46 | -------------------------------------------------------------------------------- /src/seccomp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Logswan 2.1.15 3 | * Copyright (c) 2015-2025, Frederic Cambus 4 | * https://www.logswan.org 5 | * 6 | * Created: 2015-05-31 7 | * Last Updated: 2020-09-17 8 | * 9 | * Logswan is released under the BSD 2-Clause license. 10 | * See LICENSE file for details. 11 | * 12 | * SPDX-License-Identifier: BSD-2-Clause 13 | */ 14 | 15 | #ifndef SECCOMP_H 16 | #define SECCOMP_H 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #if defined(__i386__) 27 | #define SECCOMP_AUDIT_ARCH AUDIT_ARCH_I386 28 | #elif defined(__x86_64__) 29 | #define SECCOMP_AUDIT_ARCH AUDIT_ARCH_X86_64 30 | #elif defined(__arm__) 31 | #define SECCOMP_AUDIT_ARCH AUDIT_ARCH_ARM 32 | #elif defined(__aarch64__) 33 | #define SECCOMP_AUDIT_ARCH AUDIT_ARCH_AARCH64 34 | #else 35 | #error "Seccomp is only supported on i386, x86_64, arm, and aarch64 architectures." 36 | #endif 37 | 38 | #define LOGSWAN_SYSCALL_ALLOW(syscall) \ 39 | BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##syscall, 0, 1), \ 40 | BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) 41 | 42 | static struct sock_filter filter[] = { 43 | /* Validate architecture */ 44 | BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, arch)), 45 | BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_AUDIT_ARCH, 1, 0), 46 | BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL), 47 | 48 | /* Load syscall */ 49 | BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)), 50 | 51 | LOGSWAN_SYSCALL_ALLOW(brk), 52 | LOGSWAN_SYSCALL_ALLOW(clock_gettime), /* i386 glibc */ 53 | LOGSWAN_SYSCALL_ALLOW(close), 54 | LOGSWAN_SYSCALL_ALLOW(dup), 55 | LOGSWAN_SYSCALL_ALLOW(exit_group), 56 | LOGSWAN_SYSCALL_ALLOW(fcntl), 57 | #if defined(__NR_fcntl64) 58 | LOGSWAN_SYSCALL_ALLOW(fcntl64), /* i386 musl */ 59 | #endif 60 | LOGSWAN_SYSCALL_ALLOW(fstat), 61 | #if defined(__NR_fstat64) 62 | LOGSWAN_SYSCALL_ALLOW(fstat64), /* i386 glibc */ 63 | #endif 64 | LOGSWAN_SYSCALL_ALLOW(ioctl), 65 | LOGSWAN_SYSCALL_ALLOW(lseek), 66 | #if defined(__NR__llseek) 67 | LOGSWAN_SYSCALL_ALLOW(_llseek), /* i386 glibc */ 68 | #endif 69 | #if defined(__NR_open) 70 | LOGSWAN_SYSCALL_ALLOW(open), 71 | #endif 72 | LOGSWAN_SYSCALL_ALLOW(openat), 73 | #if defined(__NR_mmap) 74 | LOGSWAN_SYSCALL_ALLOW(mmap), 75 | #endif 76 | #if defined(__NR_mmap2) 77 | LOGSWAN_SYSCALL_ALLOW(mmap2), /* i386 glibc */ 78 | #endif 79 | LOGSWAN_SYSCALL_ALLOW(munmap), 80 | LOGSWAN_SYSCALL_ALLOW(read), 81 | LOGSWAN_SYSCALL_ALLOW(write), 82 | LOGSWAN_SYSCALL_ALLOW(writev), 83 | 84 | BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) 85 | }; 86 | 87 | struct sock_fprog logswan = { 88 | .len = sizeof(filter)/sizeof(filter[0]), 89 | .filter = filter 90 | }; 91 | 92 | #endif /* SECCOMP_H */ 93 | -------------------------------------------------------------------------------- /tests/invalid.log: -------------------------------------------------------------------------------- 1 | 1.1.x.1 - - [09/Dec/2018:10:59:20 +0100] "HEAD / HTTP/1.1" 200 8142 "" "curl/7.62.0" 2 | ::ffff:1x1:101 - - [09/Dec/2018:10:59:20 +0100] "HEAD / HTTP/1.1" 200 8142 "" "curl/7.62.0" 3 | - - >o_/ 4 | -------------------------------------------------------------------------------- /tests/logswan.log: -------------------------------------------------------------------------------- 1 | 1.1.1.1 - - [09/Dec/2018:10:59:20 +0100] "HEAD / HTTP/1.1" 200 8142 "" "curl/7.62.0" 2 | 1.1.1.1 - - [09/Dec/2018:10:59:26 +0100] "GET / HTTP/1.1" 200 8142 "" "curl/7.62.0" 3 | 1.1.1.1 - - [09/Dec/2018:11:00:02 +0100] "GET /robots.txt HTTP/1.1" 404 0 "" "curl/7.62.0" 4 | 1.1.1.1 - - [09/Dec/2018:11:06:22 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 5 | 1.1.1.1 - - [09/Dec/2018:11:06:22 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 6 | 1.1.1.1 - - [09/Dec/2018:11:06:23 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "https://www.logswan.org/" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 7 | 2.2.2.2 - - [09/Dec/2018:12:36:38 +0100] "GET /files/logswan-1.00.tar.gz HTTP/1.1" 200 14571 "" "curl/7.62.0" 8 | 2.2.2.2 - - [09/Dec/2018:12:36:47 +0100] "GET /files/logswan-1.00.tar.gz HTTP/1.1" 200 14571 "" "Wget/1.19.5 (openbsd6.4)" 9 | 2.2.2.2 - - [09/Dec/2018:12:36:51 +0100] "GET /files/logswan-1.01.tar.gz HTTP/1.1" 200 14790 "" "Wget/1.19.5 (openbsd6.4)" 10 | 2.2.2.2 - - [09/Dec/2018:12:36:53 +0100] "GET /files/logswan-1.02.tar.gz HTTP/1.1" 200 14931 "" "Wget/1.19.5 (openbsd6.4)" 11 | 2.2.2.2 - - [09/Dec/2018:12:36:55 +0100] "GET /files/logswan-1.03.tar.gz HTTP/1.1" 200 16485 "" "Wget/1.19.5 (openbsd6.4)" 12 | 2.2.2.2 - - [09/Dec/2018:12:36:57 +0100] "GET /files/logswan-1.04.tar.gz HTTP/1.1" 200 16913 "" "Wget/1.19.5 (openbsd6.4)" 13 | 2.2.2.2 - - [09/Dec/2018:12:37:00 +0100] "GET /files/logswan-1.05.tar.gz HTTP/1.1" 200 17392 "" "Wget/1.19.5 (openbsd6.4)" 14 | 2.2.2.2 - - [09/Dec/2018:12:37:02 +0100] "GET /files/logswan-1.06.tar.gz HTTP/1.1" 200 17589 "" "Wget/1.19.5 (openbsd6.4)" 15 | 2.2.2.2 - - [09/Dec/2018:12:37:05 +0100] "GET /files/logswan-1.07.tar.gz HTTP/1.1" 200 18697 "" "Wget/1.19.5 (openbsd6.4)" 16 | 2.2.2.2 - - [09/Dec/2018:12:37:13 +0100] "GET /files/logswan-2.0.0.tar.gz HTTP/1.1" 200 20683 "" "Wget/1.19.5 (openbsd6.4)" 17 | 2.2.2.2 - - [09/Dec/2018:12:37:16 +0100] "GET /files/logswan-2.0.1.tar.gz HTTP/1.1" 200 21336 "" "Wget/1.19.5 (openbsd6.4)" 18 | 2.2.2.2 - - [09/Dec/2018:12:37:18 +0100] "GET /files/logswan-2.0.2.tar.gz HTTP/1.1" 200 21367 "" "Wget/1.19.5 (openbsd6.4)" 19 | 2.2.2.2 - - [09/Dec/2018:12:37:21 +0100] "GET /files/logswan-2.0.3.tar.gz HTTP/1.1" 200 21799 "" "Wget/1.19.5 (openbsd6.4)" 20 | 3.3.3.3 - - [09/Dec/2018:13:16:24 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 21 | 3.3.3.3 - - [09/Dec/2018:13:16:24 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 22 | 4.4.4.4 - - [09/Dec/2018:14:32:28 +0100] "GET / HTTP/1.1" 200 8142 "" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 23 | 4.4.4.4 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 24 | 4.4.4.4 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 25 | 4.4.4.4 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 26 | 4.4.4.4 - - [09/Dec/2018:14:32:29 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 27 | 5.5.5.5 - - [09/Dec/2018:15:21:57 +0100] "GET / HTTP/1.0" 200 8142 "" "Lynx/2.8.9rel.1 libwww-FM/2.14 SSL-MM/1.4.1" 28 | 6.6.6.6 - - [09/Dec/2018:16:49:12 +0100] "GET / HTTP/1.1" 200 8142 "https://www.logswan.org/" "Dillo/3.0.5" 29 | 6.6.6.6 - - [09/Dec/2018:16:49:12 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "Dillo/3.0.5" 30 | 7.7.7.7 - - [09/Dec/2018:18:17:29 +0100] "GET / HTTP/1.1" 200 8142 "" "NetSurf/3.8 (OpenBSD)" 31 | 7.7.7.7 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 32 | 7.7.7.7 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 33 | 7.7.7.7 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 34 | 7.7.7.7 - - [09/Dec/2018:18:17:31 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "" "NetSurf/3.8 (OpenBSD)" 35 | ::ffff:101:101 - - [09/Dec/2018:10:59:20 +0100] "HEAD / HTTP/1.1" 200 8142 "" "curl/7.62.0" 36 | ::ffff:101:101 - - [09/Dec/2018:10:59:26 +0100] "GET / HTTP/1.1" 200 8142 "" "curl/7.62.0" 37 | ::ffff:101:101 - - [09/Dec/2018:11:00:02 +0100] "GET /robots.txt HTTP/1.1" 404 0 "" "curl/7.62.0" 38 | ::ffff:101:101 - - [09/Dec/2018:11:06:22 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 39 | ::ffff:101:101 - - [09/Dec/2018:11:06:22 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 40 | ::ffff:101:101 - - [09/Dec/2018:11:06:23 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "https://www.logswan.org/" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 41 | ::ffff:202:202 - - [09/Dec/2018:12:36:38 +0100] "GET /files/logswan-1.00.tar.gz HTTP/1.1" 200 14571 "" "curl/7.62.0" 42 | ::ffff:202:202 - - [09/Dec/2018:12:36:47 +0100] "GET /files/logswan-1.00.tar.gz HTTP/1.1" 200 14571 "" "Wget/1.19.5 (openbsd6.4)" 43 | ::ffff:202:202 - - [09/Dec/2018:12:36:51 +0100] "GET /files/logswan-1.01.tar.gz HTTP/1.1" 200 14790 "" "Wget/1.19.5 (openbsd6.4)" 44 | ::ffff:202:202 - - [09/Dec/2018:12:36:53 +0100] "GET /files/logswan-1.02.tar.gz HTTP/1.1" 200 14931 "" "Wget/1.19.5 (openbsd6.4)" 45 | ::ffff:202:202 - - [09/Dec/2018:12:36:55 +0100] "GET /files/logswan-1.03.tar.gz HTTP/1.1" 200 16485 "" "Wget/1.19.5 (openbsd6.4)" 46 | ::ffff:202:202 - - [09/Dec/2018:12:36:57 +0100] "GET /files/logswan-1.04.tar.gz HTTP/1.1" 200 16913 "" "Wget/1.19.5 (openbsd6.4)" 47 | ::ffff:202:202 - - [09/Dec/2018:12:37:00 +0100] "GET /files/logswan-1.05.tar.gz HTTP/1.1" 200 17392 "" "Wget/1.19.5 (openbsd6.4)" 48 | ::ffff:202:202 - - [09/Dec/2018:12:37:02 +0100] "GET /files/logswan-1.06.tar.gz HTTP/1.1" 200 17589 "" "Wget/1.19.5 (openbsd6.4)" 49 | ::ffff:202:202 - - [09/Dec/2018:12:37:05 +0100] "GET /files/logswan-1.07.tar.gz HTTP/1.1" 200 18697 "" "Wget/1.19.5 (openbsd6.4)" 50 | ::ffff:202:202 - - [09/Dec/2018:12:37:13 +0100] "GET /files/logswan-2.0.0.tar.gz HTTP/1.1" 200 20683 "" "Wget/1.19.5 (openbsd6.4)" 51 | ::ffff:202:202 - - [09/Dec/2018:12:37:16 +0100] "GET /files/logswan-2.0.1.tar.gz HTTP/1.1" 200 21336 "" "Wget/1.19.5 (openbsd6.4)" 52 | ::ffff:202:202 - - [09/Dec/2018:12:37:18 +0100] "GET /files/logswan-2.0.2.tar.gz HTTP/1.1" 200 21367 "" "Wget/1.19.5 (openbsd6.4)" 53 | ::ffff:202:202 - - [09/Dec/2018:12:37:21 +0100] "GET /files/logswan-2.0.3.tar.gz HTTP/1.1" 200 21799 "" "Wget/1.19.5 (openbsd6.4)" 54 | ::ffff:303:303 - - [09/Dec/2018:13:16:24 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 55 | ::ffff:303:303 - - [09/Dec/2018:13:16:24 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36" 56 | ::ffff:404:404 - - [09/Dec/2018:14:32:28 +0100] "GET / HTTP/1.1" 200 8142 "" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 57 | ::ffff:404:404 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 58 | ::ffff:404:404 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 59 | ::ffff:404:404 - - [09/Dec/2018:14:32:29 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/assets/css/style.css" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 60 | ::ffff:404:404 - - [09/Dec/2018:14:32:29 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "" "Mozilla/5.0 (X11; OpenBSD amd64; rv:63.0) Gecko/20100101 Firefox/63.0" 61 | ::ffff:505:505 - - [09/Dec/2018:15:21:57 +0100] "GET / HTTP/1.0" 200 8142 "" "Lynx/2.8.9rel.1 libwww-FM/2.14 SSL-MM/1.4.1" 62 | ::ffff:606:606 - - [09/Dec/2018:16:49:12 +0100] "GET / HTTP/1.1" 200 8142 "https://www.logswan.org/" "Dillo/3.0.5" 63 | ::ffff:606:606 - - [09/Dec/2018:16:49:12 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "Dillo/3.0.5" 64 | ::ffff:707:707 - - [09/Dec/2018:18:17:29 +0100] "GET / HTTP/1.1" 200 8142 "" "NetSurf/3.8 (OpenBSD)" 65 | ::ffff:707:707 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/css/style.css HTTP/1.1" 200 5466 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 66 | ::ffff:707:707 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/images/bkg.png HTTP/1.1" 404 0 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 67 | ::ffff:707:707 - - [09/Dec/2018:18:17:30 +0100] "GET /assets/images/blacktocat.png HTTP/1.1" 404 0 "https://www.logswan.org/" "NetSurf/3.8 (OpenBSD)" 68 | ::ffff:707:707 - - [09/Dec/2018:18:17:31 +0100] "GET /favicon.ico HTTP/1.1" 404 0 "" "NetSurf/3.8 (OpenBSD)" 69 | -------------------------------------------------------------------------------- /tests/logswan.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcambus/logswan/e4ca22d542bc36a4027d09f1b307949c6d54b8d9/tests/logswan.mmdb -------------------------------------------------------------------------------- /tests/logswan.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | use MaxMind::DB::Writer::Tree; 6 | use Text::CSV; 7 | 8 | my %types = ( 9 | names => 'map', 10 | continent => 'map', 11 | country => 'map', 12 | code => 'utf8_string', 13 | iso_code => 'utf8_string', 14 | en => 'utf8_string' 15 | ); 16 | 17 | my $tree = MaxMind::DB::Writer::Tree->new( 18 | ip_version => 6, 19 | record_size => 32, 20 | database_type => 'Logswan', 21 | languages => ['en'], 22 | description => { en => 'Logswan IP geolocation test data' }, 23 | map_key_type_callback => sub { $types{ $_[0] } } 24 | ); 25 | 26 | my $au = { 27 | continent => { code => "OC" }, 28 | country => { iso_code => "AU", names => { en => "Australia" } } 29 | }; 30 | 31 | my $de = { 32 | continent => { code => "EU" }, 33 | country => { iso_code => "DE", names => { en => "Germany" } } 34 | }; 35 | 36 | my $fr = { 37 | continent => { code => "EU" }, 38 | country => { iso_code => "FR", names => { en => "France" } } 39 | }; 40 | 41 | my $us = { 42 | continent => { code => "NA" }, 43 | country => { iso_code => "US", names => { en => "United States" } } 44 | }; 45 | 46 | $tree->insert_network( '1.1.1.1/32', $au ); 47 | $tree->insert_network( '2.2.2.2/32', $fr ); 48 | $tree->insert_network( '3.3.3.3/32', $us ); 49 | $tree->insert_network( '4.4.4.4/32', $us ); 50 | $tree->insert_network( '5.5.5.5/32', $de ); 51 | $tree->insert_network( '6.6.6.6/32', $us ); 52 | $tree->insert_network( '7.7.7.7/32', $us ); 53 | 54 | $tree->insert_network( '::ffff:101:101/128', $au ); 55 | $tree->insert_network( '::ffff:202:202/128', $fr ); 56 | $tree->insert_network( '::ffff:303:303/128', $us ); 57 | $tree->insert_network( '::ffff:404:404/128', $us ); 58 | $tree->insert_network( '::ffff:505:505/128', $de ); 59 | $tree->insert_network( '::ffff:606:606/128', $us ); 60 | $tree->insert_network( '::ffff:707:707/128', $us ); 61 | 62 | open my $db, '>:raw', 'logswan.mmdb'; 63 | $tree->write_tree($db); 64 | --------------------------------------------------------------------------------