feat: first init

- Simple unix daemon
- Python, Go, and C client
This commit is contained in:
darwincereska
2026-04-13 22:34:27 -04:00
commit 4a0aabce43
22 changed files with 3527 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# Build files
build/
.cache/
.task/
compile_commands.json
# MacOS
.DS_Store
+2997
View File
File diff suppressed because it is too large Load Diff
View File
+85
View File
@@ -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
View File
+77
View File
@@ -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;
}
+15
View File
@@ -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
+40
View File
@@ -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()
}
+16
View File
@@ -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
View File
Binary file not shown.
+13
View File
@@ -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;
}
+14
View File
@@ -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")
}
+8
View File
@@ -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()
+20
View File
@@ -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
View File
+12
View File
@@ -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
+20
View File
@@ -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
View File
@@ -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)
+13
View File
@@ -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);
}
+103
View File
@@ -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;
}
+41
View File
@@ -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
View File
@@ -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);
}