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