mirror of
https://github.com/log-chipper/chipper.git
synced 2026-06-11 09:13:23 -05:00
feat: first init
- Simple unix daemon - Python, Go, and C client
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
# Build files
|
||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
.task/
|
||||||
|
compile_commands.json
|
||||||
|
|
||||||
|
# MacOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
vars:
|
||||||
|
BUILD_DIR: build
|
||||||
|
CC: ninja
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
clean:
|
||||||
|
desc: Cleans up build artifacts
|
||||||
|
cmds:
|
||||||
|
- rm -rf build
|
||||||
|
- rm -rf pkg
|
||||||
|
- rm -rf docs
|
||||||
|
status:
|
||||||
|
- test ! -d build
|
||||||
|
- test ! -d pkg
|
||||||
|
- test ! -d docs
|
||||||
|
|
||||||
|
setup:
|
||||||
|
desc: Sets up build directory
|
||||||
|
preconditions:
|
||||||
|
- sh: which meson
|
||||||
|
msg: Missing 'meson' please add it to your PATH
|
||||||
|
- sh: which {{ .CC }}
|
||||||
|
msg: Missing '{{ .CC }}' please add it to your PATH'
|
||||||
|
sources:
|
||||||
|
- meson.build
|
||||||
|
cmds:
|
||||||
|
- meson setup --reconfigure {{ .BUILD_DIR }} --backend={{ .CC }}
|
||||||
|
- ln -sf {{ .BUILD_DIR }}/compile_commands.json .
|
||||||
|
generates:
|
||||||
|
- "{{ .BUILD_DIR }}/build.ninja"
|
||||||
|
- "{{ .BUILD_DIR }}/compile_commands.json"
|
||||||
|
|
||||||
|
build:
|
||||||
|
desc: Builds the main binary
|
||||||
|
deps: [setup]
|
||||||
|
cmds:
|
||||||
|
- meson compile -C {{ .BUILD_DIR }}
|
||||||
|
sources:
|
||||||
|
- src/**/*.c
|
||||||
|
- include/**/*.h
|
||||||
|
- meson.build
|
||||||
|
generates:
|
||||||
|
- "{{ .BUILD_DIR }}/chipper"
|
||||||
|
|
||||||
|
build:watch:
|
||||||
|
desc: Watches and builds main binary
|
||||||
|
deps: [build]
|
||||||
|
watch: true
|
||||||
|
cmds:
|
||||||
|
- task: build
|
||||||
|
|
||||||
|
run:
|
||||||
|
desc: Builds and runs main binary
|
||||||
|
deps: [build]
|
||||||
|
cmd: "./{{ .BUILD_DIR }}/chipper {{ .CLI_ARGS }}"
|
||||||
|
|
||||||
|
run:watch:
|
||||||
|
desc: Watches for changes and runs main binary
|
||||||
|
watch: true
|
||||||
|
cmds:
|
||||||
|
- task: run
|
||||||
|
|
||||||
|
docs:
|
||||||
|
desc: Generates documentation pages
|
||||||
|
preconditions:
|
||||||
|
- sh: which doxygen
|
||||||
|
msg: Missing 'doxygen' please add it to your PATH
|
||||||
|
cmd: doxygen Doxyfile
|
||||||
|
sources:
|
||||||
|
- src/**/*.c
|
||||||
|
- include/**/*.h
|
||||||
|
generates:
|
||||||
|
- docs/**
|
||||||
|
|
||||||
|
docs:open:
|
||||||
|
desc: Opens docs in your browser
|
||||||
|
deps: [docs]
|
||||||
|
cmds:
|
||||||
|
- open docs/html/index.html
|
||||||
|
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- task: run
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* @file chipper_client.c
|
||||||
|
* @brief The C client sdk for connecting to the Chipper Socket
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "chipper_client.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define CHIPPER_CLIENT_BUFFER 2048
|
||||||
|
|
||||||
|
static int chipper_fd = -1;
|
||||||
|
static struct sockaddr_un chipper_addr;
|
||||||
|
static char chipper_service[64];
|
||||||
|
static char chipper_source[64];
|
||||||
|
|
||||||
|
int chipper_client_init(const char *socket_path, const char *service, const char *source) {
|
||||||
|
if (chipper_fd != -1) return 0; // Already initialized
|
||||||
|
|
||||||
|
chipper_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||||
|
if (chipper_fd == -1) {
|
||||||
|
perror("chipper_client socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&chipper_addr, 0, sizeof(chipper_addr));
|
||||||
|
chipper_addr.sun_family = AF_UNIX;
|
||||||
|
strncpy(chipper_addr.sun_path, socket_path, sizeof(chipper_addr.sun_path) - 1);
|
||||||
|
strncpy(chipper_service, service, sizeof(chipper_service) -1);
|
||||||
|
chipper_service[sizeof(chipper_service) - 1] = '\0';
|
||||||
|
|
||||||
|
strncpy(chipper_source, source, sizeof(chipper_source) - 1);
|
||||||
|
chipper_source[sizeof(chipper_source) - 1] = '\0';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chipper_client_close(void) {
|
||||||
|
if (chipper_fd != -1) {
|
||||||
|
close(chipper_fd);
|
||||||
|
chipper_fd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int chipper_client_log(const char *level, const char *fmt, ...) {
|
||||||
|
if (chipper_fd == -1) {
|
||||||
|
errno = EBADF;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char msg_buf[CHIPPER_CLIENT_BUFFER];
|
||||||
|
char final_buf[CHIPPER_CLIENT_BUFFER];
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(msg_buf, sizeof(msg_buf), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
snprintf(final_buf, sizeof(final_buf),
|
||||||
|
"level=%s service=%s source=%s msg=\"%s\"",
|
||||||
|
level, chipper_service, chipper_source, msg_buf
|
||||||
|
);
|
||||||
|
|
||||||
|
ssize_t n = sendto(chipper_fd, final_buf, strlen(final_buf), 0, (struct sockaddr *)&chipper_addr, sizeof(chipper_addr));
|
||||||
|
if (n == -1) {
|
||||||
|
perror("chipper_client sendto");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @file chipper_client.h
|
||||||
|
* @brief The C client sdk for connecting to the Chipper Socket
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CHIPPER_CLIENT_H
|
||||||
|
#define CHIPPER_CLIENT_H
|
||||||
|
|
||||||
|
int chipper_client_init(const char *socket_path, const char *service, const char *source);
|
||||||
|
|
||||||
|
void chipper_client_close(void);
|
||||||
|
|
||||||
|
int chipper_client_log(const char *level, const char *fmt, ...);
|
||||||
|
|
||||||
|
#endif // CHIPPER_CLIENT_H
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package chipper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
conn *net.UnixConn
|
||||||
|
service string
|
||||||
|
source string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(socketPath, service, source string) (*Client, error) {
|
||||||
|
addr := &net.UnixAddr{Name: socketPath, Net: "unixgram"}
|
||||||
|
conn, err := net.DialUnix("unixgram", nil, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
conn: conn,
|
||||||
|
service: service,
|
||||||
|
source: source,
|
||||||
|
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Log(level, msg string) error {
|
||||||
|
line := fmt.Sprintf(
|
||||||
|
"level=%s service=%s source=%s msg=\"%s\"",
|
||||||
|
level, c.service, c.source, msg,
|
||||||
|
)
|
||||||
|
_, err := c.conn.Write([]byte(line))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
class ChipperClient:
|
||||||
|
def __init__(self, socket_path: str ="/tmp/chipper.sock", service: str = "myapp", source: str = "worker") -> None:
|
||||||
|
self.socket_path = socket_path
|
||||||
|
self.service = service
|
||||||
|
self.source = source
|
||||||
|
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
|
def log(self, level: str, msg: str) -> None:
|
||||||
|
# level=info service=pyapp source=worker msg="something"
|
||||||
|
line = f'level={level} service={self.service} source={self.source} msg="{msg}"'
|
||||||
|
self.sock.sendto(line.encode("utf-8"), self.socket_path)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
self.sock.close()
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,13 @@
|
|||||||
|
#include "../clients/c/chipper_client.h"
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
if (chipper_client_init("/tmp/chipper.sock", "myapp", "worker") != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
chipper_client_log("info", "hello from C client, user_id=%d", 42);
|
||||||
|
chipper_client_log("error", "something failed: %s", "oops");
|
||||||
|
|
||||||
|
chipper_client_close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/log-chipper/chipper"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client, err := chipper.NewClient()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
client.Log("info", "hello from go")
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from ..clients.python.chipper_client import *
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
c: ChipperClient = ChipperClient(service="python_example", source="example")
|
||||||
|
c.log("info", "hello from python example")
|
||||||
|
c.log("error", "something failed")
|
||||||
|
c.close()
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @file chipper_common.h
|
||||||
|
* @brief Common definitions and small helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CHIPPER_COMMON_H
|
||||||
|
#define CHIPPER_COMMON_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define CHIPPER_BUFFER_SIZE 4096
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *socket_path;
|
||||||
|
const char *log_path;
|
||||||
|
} ChipperOptions;
|
||||||
|
|
||||||
|
void chipper_die(const char *msg);
|
||||||
|
|
||||||
|
#endif // CHIPPER_COMMON_H
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file chipper_daemon
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CHIPPER_DAEMON_H
|
||||||
|
#define CHIPPER_DAEMON_H
|
||||||
|
|
||||||
|
#include "chipper_common.h"
|
||||||
|
|
||||||
|
int chipper_run_daemon(const ChipperOptions *opts);
|
||||||
|
|
||||||
|
#endif // CHIPPER_DAEMON_H
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @file chipper_log.h
|
||||||
|
* @brief Low-level log output
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CHIPPER_LOG_H
|
||||||
|
#define CHIPPER_LOG_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *path;
|
||||||
|
FILE *file;
|
||||||
|
} ChipperLog;
|
||||||
|
|
||||||
|
int chipper_log_open(ChipperLog *log, const char *path);
|
||||||
|
void chipper_log_close(ChipperLog *log);
|
||||||
|
int chipper_log_write(ChipperLog *log, const char *timestamp, const char *line);
|
||||||
|
|
||||||
|
#endif // CHIPPER_LOG_H
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
project(
|
||||||
|
'chipper',
|
||||||
|
'c',
|
||||||
|
meson_version : '>= 1.3.0',
|
||||||
|
version : '0.0.1',
|
||||||
|
default_options : ['warning_level=3'],
|
||||||
|
)
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
sources = [
|
||||||
|
'src/main.c',
|
||||||
|
'src/chipper_daemon.c',
|
||||||
|
'src/chipper_log.c',
|
||||||
|
'src/chipper_common.c'
|
||||||
|
]
|
||||||
|
|
||||||
|
exe = executable(
|
||||||
|
'chipper',
|
||||||
|
sources,
|
||||||
|
dependencies : dependencies,
|
||||||
|
include_directories : 'include',
|
||||||
|
install : true,
|
||||||
|
)
|
||||||
|
|
||||||
|
test('basic', exe)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @file chipper_common.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "chipper_common.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void chipper_die(const char *msg) {
|
||||||
|
fprintf(stderr, "chipper error: %s: %s\n", msg, strerror(errno));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* @file chipper_daemon.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "chipper_daemon.h"
|
||||||
|
#include "chipper_log.h"
|
||||||
|
#include "chipper_common.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *socket_path;
|
||||||
|
int sock_fd;
|
||||||
|
ChipperLog log;
|
||||||
|
} ChipperDaemon;
|
||||||
|
|
||||||
|
static void get_iso8601_utc(char *buf, size_t len) {
|
||||||
|
time_t now = time(NULL);
|
||||||
|
struct tm t;
|
||||||
|
gmtime_r(&now, &t);
|
||||||
|
strftime(buf, len, "%Y-%m-%dT%H:%M:%SZ", &t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void daemon_cleanup(ChipperDaemon *d) {
|
||||||
|
if (d->sock_fd >= 0) {
|
||||||
|
close(d->sock_fd);
|
||||||
|
d->sock_fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d->socket_path) {
|
||||||
|
unlink(d->socket_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
chipper_log_close(&d->log);
|
||||||
|
}
|
||||||
|
|
||||||
|
int chipper_run_daemon(const ChipperOptions *opts) {
|
||||||
|
ChipperDaemon d;
|
||||||
|
memset(&d, 0, sizeof(d));
|
||||||
|
d.socket_path = opts->socket_path;
|
||||||
|
d.sock_fd = -1;
|
||||||
|
|
||||||
|
// Open the log
|
||||||
|
if (chipper_log_open(&d.log, opts->log_path) != 0) {
|
||||||
|
chipper_die("open log file");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the socket
|
||||||
|
d.sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||||
|
if (d.sock_fd == -1) {
|
||||||
|
chipper_die("socket");
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink(d.socket_path);
|
||||||
|
|
||||||
|
struct sockaddr_un addr;
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
strncpy(addr.sun_path, d.socket_path, sizeof(addr.sun_path) - 1);
|
||||||
|
|
||||||
|
if (bind(d.sock_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
|
||||||
|
daemon_cleanup(&d);
|
||||||
|
chipper_die("bind");
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Chipper listening on %s, writing to %s\n", d.socket_path, d.log.path);
|
||||||
|
|
||||||
|
char buf[CHIPPER_BUFFER_SIZE];
|
||||||
|
char ts[64];
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
ssize_t n = recvfrom(d.sock_fd, buf, sizeof(buf) - 1, 0, NULL, NULL);
|
||||||
|
if (n == -1) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
perror("recvfrom");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[n] = '\0';
|
||||||
|
|
||||||
|
size_t len = strlen(buf);
|
||||||
|
if (len > 0 && buf[len - 1] == '\n') {
|
||||||
|
buf[len - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
get_iso8601_utc(ts, sizeof(ts));
|
||||||
|
|
||||||
|
if (chipper_log_write(&d.log, ts, buf) != 0) {
|
||||||
|
fprintf(stderr, "failed to write log line\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
daemon_cleanup(&d);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @file chipper_log.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "chipper_log.h"
|
||||||
|
#include "chipper_common.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Opens a chipper log file
|
||||||
|
*/
|
||||||
|
int chipper_log_open(ChipperLog *log, const char *path) {
|
||||||
|
memset(log, 0, sizeof(*log));
|
||||||
|
log->path = path;
|
||||||
|
log->file = fopen(path, "a");
|
||||||
|
if (!log->file) return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Closes a chipper log file
|
||||||
|
* @param log Pointer to ChipperLog
|
||||||
|
*/
|
||||||
|
void chipper_log_close(ChipperLog *log) {
|
||||||
|
if (log->file) {
|
||||||
|
fclose(log->file);
|
||||||
|
log->file = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Writes to a chipper log
|
||||||
|
*/
|
||||||
|
int chipper_log_write(ChipperLog *log, const char *timestamp, const char *line) {
|
||||||
|
if (!log->file) return -1;
|
||||||
|
if (fprintf(log->file, "[%s] %s\n", timestamp, line) < 0) return -1;
|
||||||
|
|
||||||
|
fflush(log->file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#include "chipper_daemon.h"
|
||||||
|
#include "chipper_common.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc != 3) {
|
||||||
|
fprintf(stderr, "Usage: %s <socket_path> <log_file>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChipperOptions opts = {
|
||||||
|
.socket_path = argv[1],
|
||||||
|
.log_path = argv[2],
|
||||||
|
};
|
||||||
|
|
||||||
|
return chipper_run_daemon(&opts);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user