mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
Simplify DevelopmentLogger, add ProductionLogger
Add auth helper to create a user from CLI: ``` jetzig auth user:create user@example.com ```
This commit is contained in:
parent
d219a3ce83
commit
a6d1b92f5e
21
build.zig
21
build.zig
@ -127,8 +127,6 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
return error.ZmplVersionNotSupported;
|
return error.ZmplVersionNotSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = b.option([]const u8, "seed", "Internal test seed");
|
|
||||||
|
|
||||||
const target = b.host;
|
const target = b.host;
|
||||||
const optimize = exe.root_module.optimize orelse .Debug;
|
const optimize = exe.root_module.optimize orelse .Debug;
|
||||||
|
|
||||||
@ -333,6 +331,25 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const auth_user_create_step = b.step("jetzig:auth:user:create", "List all routes in your app");
|
||||||
|
const exe_auth = b.addExecutable(.{
|
||||||
|
.name = "auth",
|
||||||
|
.root_source_file = jetzig_dep.path("src/commands/auth.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
exe_auth.root_module.addImport("jetquery", jetquery_module);
|
||||||
|
exe_auth.root_module.addImport("jetzig", jetzig_module);
|
||||||
|
exe_auth.root_module.addImport("jetcommon", jetcommon_module);
|
||||||
|
exe_auth.root_module.addImport("Schema", schema_module);
|
||||||
|
exe_auth.root_module.addImport("main", main_module);
|
||||||
|
const run_auth_user_create_cmd = b.addRunArtifact(exe_auth);
|
||||||
|
auth_user_create_step.dependOn(&run_auth_user_create_cmd.step);
|
||||||
|
run_auth_user_create_cmd.addArg("user:create");
|
||||||
|
if (b.option([]const u8, "auth_username", "Auth username")) |username| {
|
||||||
|
run_auth_user_create_cmd.addArg(username);
|
||||||
|
}
|
||||||
|
|
||||||
const exe_database = b.addExecutable(.{
|
const exe_database = b.addExecutable(.{
|
||||||
.name = "database",
|
.name = "database",
|
||||||
.root_source_file = jetzig_dep.path("src/commands/database.zig"),
|
.root_source_file = jetzig_dep.path("src/commands/database.zig"),
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
.hash = "12201d75d73aad5e1c996de4d5ae87a00e58479c8d469bc2eeb5fdeeac8857bc09af",
|
.hash = "12201d75d73aad5e1c996de4d5ae87a00e58479c8d469bc2eeb5fdeeac8857bc09af",
|
||||||
},
|
},
|
||||||
.jetquery = .{
|
.jetquery = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetquery/archive/c9df298b5a2d0713257c99d89096f6f981cbc6b6.tar.gz",
|
.url = "https://github.com/jetzig-framework/jetquery/archive/23c9741a407b6c4f93d9d21508f6568be747bdcc.tar.gz",
|
||||||
.hash = "12200417dd5948bd2942c36b4965fb7b68a3e4582a7a74dc05bc30801597ace00849",
|
.hash = "122020374e5fd67d5836c0f3d7a8f814262aa076e3b90c1043f44184da1c2997e0bb",
|
||||||
},
|
},
|
||||||
.jetcommon = .{
|
.jetcommon = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetcommon/archive/a248776ba56d6cc2b160d593ac3305756adcd26e.tar.gz",
|
.url = "https://github.com/jetzig-framework/jetcommon/archive/a248776ba56d6cc2b160d593ac3305756adcd26e.tar.gz",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const args = @import("args");
|
const args = @import("args");
|
||||||
|
const util = @import("../util.zig");
|
||||||
|
|
||||||
/// Command line options for the `update` command.
|
/// Command line options for the `update` command.
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
@ -31,9 +32,9 @@ pub fn run(
|
|||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const alloc = arena.allocator();
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
const Action = enum { password };
|
const Action = enum { user_create };
|
||||||
const map = std.StaticStringMap(Action).initComptime(.{
|
const map = std.StaticStringMap(Action).initComptime(.{
|
||||||
.{ "password", .password },
|
.{ "user:create", .user_create },
|
||||||
});
|
});
|
||||||
|
|
||||||
const action = if (main_options.positionals.len > 0)
|
const action = if (main_options.positionals.len > 0)
|
||||||
@ -54,26 +55,19 @@ pub fn run(
|
|||||||
break :blk error.JetzigCommandError;
|
break :blk error.JetzigCommandError;
|
||||||
} else if (action) |capture|
|
} else if (action) |capture|
|
||||||
switch (capture) {
|
switch (capture) {
|
||||||
.password => blk: {
|
.user_create => blk: {
|
||||||
if (sub_args.len < 1) {
|
if (sub_args.len < 1) {
|
||||||
std.debug.print("Missing argument. Expected a password paramater.\n", .{});
|
std.debug.print("Missing argument. Expected an email/username parameter.\n", .{});
|
||||||
break :blk error.JetzigCommandError;
|
break :blk error.JetzigCommandError;
|
||||||
} else {
|
} else {
|
||||||
const hash = try hashPassword(alloc, sub_args[0]);
|
try util.execCommand(allocator, &.{
|
||||||
try std.io.getStdOut().writer().print("Password hash: {s}\n", .{hash});
|
"zig",
|
||||||
|
"build",
|
||||||
|
util.environmentBuildOption(main_options.options.environment),
|
||||||
|
try std.mem.concat(allocator, u8, &.{ "-Dauth_username=", sub_args[0] }),
|
||||||
|
"jetzig:auth:user:create",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hashPassword(allocator: std.mem.Allocator, password: []const u8) ![]const u8 {
|
|
||||||
const buf = try allocator.alloc(u8, 128);
|
|
||||||
return try std.crypto.pwhash.argon2.strHash(
|
|
||||||
password,
|
|
||||||
.{
|
|
||||||
.allocator = allocator,
|
|
||||||
.params = .{ .t = 3, .m = 32, .p = 4 },
|
|
||||||
},
|
|
||||||
buf,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -78,6 +78,16 @@ pub const jetzig_options = struct {
|
|||||||
/// Database Schema. Set to `@import("Schema")` to load `src/app/database/Schema.zig`.
|
/// Database Schema. Set to `@import("Schema")` to load `src/app/database/Schema.zig`.
|
||||||
pub const Schema = @import("Schema");
|
pub const Schema = @import("Schema");
|
||||||
|
|
||||||
|
/// HTTP cookie configuration
|
||||||
|
pub const cookies: jetzig.http.Cookies.CookieOptions = .{
|
||||||
|
.domain = switch (jetzig.environment) {
|
||||||
|
.development => "localhost",
|
||||||
|
.testing => "localhost",
|
||||||
|
.production => "www.example.com",
|
||||||
|
},
|
||||||
|
.path = "/",
|
||||||
|
};
|
||||||
|
|
||||||
/// Key-value store options. Set backend to `.file` to use a file-based store.
|
/// Key-value store options. Set backend to `.file` to use a file-based store.
|
||||||
/// When using `.file` backend, you must also set `.file_options`.
|
/// When using `.file` backend, you must also set `.file_options`.
|
||||||
/// The key-value store is exposed as `request.store` in views and is also available in as
|
/// The key-value store is exposed as `request.store` in views and is also available in as
|
||||||
|
72
src/commands/auth.zig
Normal file
72
src/commands/auth.zig
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const build_options = @import("build_options");
|
||||||
|
|
||||||
|
const jetquery = @import("jetquery");
|
||||||
|
const jetzig = @import("jetzig");
|
||||||
|
const Schema = @import("Schema");
|
||||||
|
const Action = enum { @"user:create" };
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer std.debug.assert(gpa.deinit() == .ok);
|
||||||
|
|
||||||
|
const gpa_allocator = gpa.allocator();
|
||||||
|
var arena = std.heap.ArenaAllocator.init(gpa_allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
const allocator = arena.allocator();
|
||||||
|
|
||||||
|
const args = try std.process.argsAlloc(allocator);
|
||||||
|
|
||||||
|
if (args.len < 3) return error.JetzigMissingArgument;
|
||||||
|
|
||||||
|
const map = std.StaticStringMap(Action).initComptime(.{
|
||||||
|
.{ "user:create", .@"user:create" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const action = map.get(args[1]) orelse return error.JetzigUnrecognizedArgument;
|
||||||
|
const env = try jetzig.Environment.init(allocator, .{ .silent = true });
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
.@"user:create" => {
|
||||||
|
const Repo = jetzig.jetquery.Repo(jetzig.database.adapter, Schema);
|
||||||
|
var repo = try Repo.loadConfig(
|
||||||
|
allocator,
|
||||||
|
std.enums.nameCast(jetzig.jetquery.Environment, jetzig.environment),
|
||||||
|
.{ .env = try jetzig.database.repoEnv(env), .context = .cli },
|
||||||
|
);
|
||||||
|
const model = comptime jetzig.config.get(jetzig.auth.AuthOptions, "auth").user_model;
|
||||||
|
const stdin = std.io.getStdIn();
|
||||||
|
const reader = stdin.reader();
|
||||||
|
|
||||||
|
if (stdin.isTty()) std.debug.print("Enter password: ", .{});
|
||||||
|
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
if (try reader.readUntilDelimiterOrEof(&buf, '\n')) |input| {
|
||||||
|
const password = std.mem.trim(u8, input, &std.ascii.whitespace);
|
||||||
|
const email = args[2];
|
||||||
|
|
||||||
|
try repo.insert(std.enums.nameCast(std.meta.DeclEnum(Schema), model), .{
|
||||||
|
.email = email,
|
||||||
|
.password_hash = try hashPassword(allocator, password),
|
||||||
|
});
|
||||||
|
std.debug.print("Created user: `{s}`.\n", .{email});
|
||||||
|
} else {
|
||||||
|
std.debug.print("Blank password. Exiting.\n", .{});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hashPassword(allocator: std.mem.Allocator, password: []const u8) ![]const u8 {
|
||||||
|
const buf = try allocator.alloc(u8, 128);
|
||||||
|
return try std.crypto.pwhash.argon2.strHash(
|
||||||
|
password,
|
||||||
|
.{
|
||||||
|
.allocator = allocator,
|
||||||
|
.params = .{ .t = 3, .m = 32, .p = 4 },
|
||||||
|
},
|
||||||
|
buf,
|
||||||
|
);
|
||||||
|
}
|
@ -28,7 +28,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const args = try std.process.argsAlloc(allocator);
|
const args = try std.process.argsAlloc(allocator);
|
||||||
|
|
||||||
if (args.len < 2) return error.JetzigMissingDatabaseArgument;
|
if (args.len < 2) return error.JetzigMissingArgument;
|
||||||
|
|
||||||
const map = std.StaticStringMap(Action).initComptime(.{
|
const map = std.StaticStringMap(Action).initComptime(.{
|
||||||
.{ "migrate", .migrate },
|
.{ "migrate", .migrate },
|
||||||
@ -37,7 +37,7 @@ pub fn main() !void {
|
|||||||
.{ "drop", .drop },
|
.{ "drop", .drop },
|
||||||
.{ "reflect", .reflect },
|
.{ "reflect", .reflect },
|
||||||
});
|
});
|
||||||
const action = map.get(args[1]) orelse return error.JetzigUnrecognizedDatabaseArgument;
|
const action = map.get(args[1]) orelse return error.JetzigUnrecognizedArgument;
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
.migrate => {
|
.migrate => {
|
||||||
|
@ -13,7 +13,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
log("Jetzig Routes:", .{});
|
log("Jetzig Routes:", .{});
|
||||||
|
|
||||||
const environment = jetzig.Environment.init(undefined);
|
const environment = jetzig.Environment.init(allocator, .{ .silent = true });
|
||||||
const initHook: ?*const fn (*jetzig.App) anyerror!void = if (@hasDecl(app, "init")) app.init else null;
|
const initHook: ?*const fn (*jetzig.App) anyerror!void = if (@hasDecl(app, "init")) app.init else null;
|
||||||
|
|
||||||
inline for (routes.routes) |route| max_uri_path_len = @max(route.uri_path.len + 5, max_uri_path_len);
|
inline for (routes.routes) |route| max_uri_path_len = @max(route.uri_path.len + 5, max_uri_path_len);
|
||||||
|
@ -28,7 +28,7 @@ pub const Time = jetcommon.types.Time;
|
|||||||
pub const Date = jetcommon.types.Date;
|
pub const Date = jetcommon.types.Date;
|
||||||
|
|
||||||
pub const build_options = @import("build_options");
|
pub const build_options = @import("build_options");
|
||||||
pub const environment = build_options.environment;
|
pub const environment = std.enums.nameCast(Environment.EnvironmentName, build_options.environment);
|
||||||
|
|
||||||
/// The primary interface for a Jetzig application. Create an `App` in your application's
|
/// The primary interface for a Jetzig application. Create an `App` in your application's
|
||||||
/// `src/main.zig` and call `start` to launch the application.
|
/// `src/main.zig` and call `start` to launch the application.
|
||||||
@ -85,7 +85,7 @@ pub const initHook: ?*const fn (*App) anyerror!void = if (@hasDecl(root, "init")
|
|||||||
/// Initialize a new Jetzig app. Call this from `src/main.zig` and then call
|
/// Initialize a new Jetzig app. Call this from `src/main.zig` and then call
|
||||||
/// `start(@import("routes").routes)` on the returned value.
|
/// `start(@import("routes").routes)` on the returned value.
|
||||||
pub fn init(allocator: std.mem.Allocator) !App {
|
pub fn init(allocator: std.mem.Allocator) !App {
|
||||||
const env = try Environment.init(allocator);
|
const env = try Environment.init(allocator, .{});
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.env = env,
|
.env = env,
|
||||||
|
@ -21,16 +21,61 @@ log_queue: *jetzig.loggers.LogQueue,
|
|||||||
pub const EnvironmentName = enum { development, production, testing };
|
pub const EnvironmentName = enum { development, production, testing };
|
||||||
pub const Vars = struct {
|
pub const Vars = struct {
|
||||||
env_map: std.process.EnvMap,
|
env_map: std.process.EnvMap,
|
||||||
|
env_file: ?EnvFile,
|
||||||
|
|
||||||
|
pub const EnvFile = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
hashmap: *std.StringHashMap([]const u8),
|
||||||
|
content: []const u8,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, file: std.fs.File) !EnvFile {
|
||||||
|
const stat = try file.stat();
|
||||||
|
const content = try file.readToEndAlloc(allocator, stat.size);
|
||||||
|
file.close();
|
||||||
|
const hashmap = try allocator.create(std.StringHashMap([]const u8));
|
||||||
|
hashmap.* = std.StringHashMap([]const u8).init(allocator);
|
||||||
|
var it = std.mem.tokenizeScalar(u8, content, '\n');
|
||||||
|
while (it.next()) |line| {
|
||||||
|
const stripped = jetzig.util.strip(line);
|
||||||
|
if (std.mem.startsWith(u8, stripped, "#")) continue;
|
||||||
|
const equals_index = std.mem.indexOfScalar(u8, stripped, '=') orelse continue;
|
||||||
|
const name = stripped[0..equals_index];
|
||||||
|
const value = if (equals_index + 1 < stripped.len) stripped[equals_index + 1 ..] else "";
|
||||||
|
try hashmap.put(name, jetzig.util.unquote(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .allocator = allocator, .hashmap = hashmap, .content = content };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: EnvFile) void {
|
||||||
|
self.hashmap.deinit();
|
||||||
|
self.allocator.destroy(self.hashmap);
|
||||||
|
self.allocator.free(self.content);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, env_file: ?std.fs.File) !Vars {
|
||||||
|
return .{
|
||||||
|
.env_file = if (env_file) |file| try EnvFile.init(allocator, file) else null,
|
||||||
|
.env_map = try std.process.getEnvMap(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Vars) void {
|
||||||
|
var env_map = self.env_map;
|
||||||
|
env_map.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(self: Vars, key: []const u8) ?[]const u8 {
|
pub fn get(self: Vars, key: []const u8) ?[]const u8 {
|
||||||
return self.env_map.get(key);
|
const env_file = self.env_file orelse return self.env_map.get(key);
|
||||||
|
return env_file.hashmap.get(key) orelse self.env_map.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getT(self: Vars, T: type, key: []const u8) !switch (@typeInfo(T)) {
|
pub fn getT(self: Vars, T: type, key: []const u8) !switch (@typeInfo(T)) {
|
||||||
.bool => T,
|
.bool => T,
|
||||||
else => ?T,
|
else => ?T,
|
||||||
} {
|
} {
|
||||||
const value = self.env_map.get(key) orelse return if (@typeInfo(T) == .bool)
|
const value = self.get(key) orelse return if (@typeInfo(T) == .bool)
|
||||||
false
|
false
|
||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
@ -48,11 +93,6 @@ pub const Vars = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: Vars) void {
|
|
||||||
var env_map = self.env_map;
|
|
||||||
env_map.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parseEnum(E: type, value: []const u8) ?E {
|
fn parseEnum(E: type, value: []const u8) ?E {
|
||||||
return std.meta.stringToEnum(E, value);
|
return std.meta.stringToEnum(E, value);
|
||||||
}
|
}
|
||||||
@ -65,8 +105,11 @@ const Options = struct {
|
|||||||
log: []const u8 = "-",
|
log: []const u8 = "-",
|
||||||
@"log-error": []const u8 = "-",
|
@"log-error": []const u8 = "-",
|
||||||
@"log-level": ?jetzig.loggers.LogLevel = null,
|
@"log-level": ?jetzig.loggers.LogLevel = null,
|
||||||
// TODO: Create a production logger and select default logger based on environment.
|
@"log-format": jetzig.loggers.LogFormat = switch (jetzig.environment) {
|
||||||
@"log-format": jetzig.loggers.LogFormat = .development,
|
.development, .testing => .development,
|
||||||
|
.production => .production,
|
||||||
|
},
|
||||||
|
@"env-file": []const u8 = ".env",
|
||||||
detach: bool = false,
|
detach: bool = false,
|
||||||
|
|
||||||
pub const shorthands = .{
|
pub const shorthands = .{
|
||||||
@ -95,6 +138,9 @@ const Options = struct {
|
|||||||
.detach =
|
.detach =
|
||||||
\\Run the server in the background. Must be used in conjunction with --log (default: false)
|
\\Run the server in the background. Must be used in conjunction with --log (default: false)
|
||||||
,
|
,
|
||||||
|
.@"env-file" =
|
||||||
|
\\Load environment variables from a file. Variables defined in this file take precedence over process environment variables.
|
||||||
|
,
|
||||||
.help = "Print help and exit",
|
.help = "Print help and exit",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -103,6 +149,7 @@ const Options = struct {
|
|||||||
const LaunchLogger = struct {
|
const LaunchLogger = struct {
|
||||||
stdout: std.fs.File,
|
stdout: std.fs.File,
|
||||||
stderr: std.fs.File,
|
stderr: std.fs.File,
|
||||||
|
silent: bool = false,
|
||||||
|
|
||||||
pub fn log(
|
pub fn log(
|
||||||
self: LaunchLogger,
|
self: LaunchLogger,
|
||||||
@ -110,6 +157,8 @@ const LaunchLogger = struct {
|
|||||||
comptime message: []const u8,
|
comptime message: []const u8,
|
||||||
log_args: anytype,
|
log_args: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
|
if (self.silent) return;
|
||||||
|
|
||||||
const target = @field(self, @tagName(jetzig.loggers.logTarget(level)));
|
const target = @field(self, @tagName(jetzig.loggers.logTarget(level)));
|
||||||
const writer = target.writer();
|
const writer = target.writer();
|
||||||
try writer.print(
|
try writer.print(
|
||||||
@ -119,7 +168,11 @@ const LaunchLogger = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(parent_allocator: std.mem.Allocator) !Environment {
|
pub const EnvironmentOptions = struct {
|
||||||
|
silent: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(parent_allocator: std.mem.Allocator, env_options: EnvironmentOptions) !Environment {
|
||||||
const arena = try parent_allocator.create(std.heap.ArenaAllocator);
|
const arena = try parent_allocator.create(std.heap.ArenaAllocator);
|
||||||
arena.* = std.heap.ArenaAllocator.init(parent_allocator);
|
arena.* = std.heap.ArenaAllocator.init(parent_allocator);
|
||||||
const allocator = arena.allocator();
|
const allocator = arena.allocator();
|
||||||
@ -127,12 +180,13 @@ pub fn init(parent_allocator: std.mem.Allocator) !Environment {
|
|||||||
const options = try args.parseForCurrentProcess(Options, allocator, .print);
|
const options = try args.parseForCurrentProcess(Options, allocator, .print);
|
||||||
defer options.deinit();
|
defer options.deinit();
|
||||||
|
|
||||||
|
const stdout = try getLogFile(.stdout, options.options);
|
||||||
|
const stderr = try getLogFile(.stdout, options.options);
|
||||||
|
|
||||||
const log_queue = try allocator.create(jetzig.loggers.LogQueue);
|
const log_queue = try allocator.create(jetzig.loggers.LogQueue);
|
||||||
log_queue.* = jetzig.loggers.LogQueue.init(allocator);
|
log_queue.* = jetzig.loggers.LogQueue.init(allocator);
|
||||||
try log_queue.setFiles(
|
|
||||||
try getLogFile(.stdout, options.options),
|
try log_queue.setFiles(stdout, stderr);
|
||||||
try getLogFile(.stderr, options.options),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (options.options.help) {
|
if (options.options.help) {
|
||||||
const writer = std.io.getStdErr().writer();
|
const writer = std.io.getStdErr().writer();
|
||||||
@ -140,26 +194,40 @@ pub fn init(parent_allocator: std.mem.Allocator) !Environment {
|
|||||||
std.process.exit(0);
|
std.process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const environment = std.enums.nameCast(EnvironmentName, jetzig.environment);
|
const env_file = std.fs.cwd().openFile(options.options.@"env-file", .{}) catch |err|
|
||||||
const vars = Vars{ .env_map = try std.process.getEnvMap(allocator) };
|
switch (err) {
|
||||||
|
error.FileNotFound => null,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vars = try Vars.init(allocator, env_file);
|
||||||
|
|
||||||
var launch_logger = LaunchLogger{
|
var launch_logger = LaunchLogger{
|
||||||
.stdout = try getLogFile(.stdout, options.options),
|
.stdout = stdout,
|
||||||
.stderr = try getLogFile(.stdout, options.options),
|
.stderr = stderr,
|
||||||
|
.silent = env_options.silent,
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = switch (options.options.@"log-format") {
|
const logger = switch (options.options.@"log-format") {
|
||||||
.development => jetzig.loggers.Logger{
|
.development => jetzig.loggers.Logger{
|
||||||
.development_logger = jetzig.loggers.DevelopmentLogger.init(
|
.development_logger = jetzig.loggers.DevelopmentLogger.init(
|
||||||
allocator,
|
allocator,
|
||||||
resolveLogLevel(options.options.@"log-level", environment),
|
resolveLogLevel(options.options.@"log-level", jetzig.environment),
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
.production => jetzig.loggers.Logger{
|
||||||
|
.production_logger = jetzig.loggers.ProductionLogger.init(
|
||||||
|
allocator,
|
||||||
|
resolveLogLevel(options.options.@"log-level", jetzig.environment),
|
||||||
log_queue,
|
log_queue,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
.json => jetzig.loggers.Logger{
|
.json => jetzig.loggers.Logger{
|
||||||
.json_logger = jetzig.loggers.JsonLogger.init(
|
.json_logger = jetzig.loggers.JsonLogger.init(
|
||||||
allocator,
|
allocator,
|
||||||
resolveLogLevel(options.options.@"log-level", environment),
|
resolveLogLevel(options.options.@"log-level", jetzig.environment),
|
||||||
log_queue,
|
log_queue,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -171,7 +239,7 @@ pub fn init(parent_allocator: std.mem.Allocator) !Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const secret_len = jetzig.http.Session.Cipher.key_length;
|
const secret_len = jetzig.http.Session.Cipher.key_length;
|
||||||
const secret_value = try getSecret(allocator, launch_logger, secret_len, environment);
|
const secret_value = try getSecret(allocator, launch_logger, secret_len, jetzig.environment);
|
||||||
const secret = if (secret_value.len > secret_len) secret_value[0..secret_len] else secret_value;
|
const secret = if (secret_value.len > secret_len) secret_value[0..secret_len] else secret_value;
|
||||||
|
|
||||||
if (secret.len != secret_len) {
|
if (secret.len != secret_len) {
|
||||||
@ -200,8 +268,9 @@ pub fn init(parent_allocator: std.mem.Allocator) !Environment {
|
|||||||
"Using `{s}` database adapter with database: `{s}`.",
|
"Using `{s}` database adapter with database: `{s}`.",
|
||||||
.{
|
.{
|
||||||
@tagName(jetzig.database.adapter),
|
@tagName(jetzig.database.adapter),
|
||||||
switch (environment) {
|
switch (jetzig.environment) {
|
||||||
inline else => |tag| @field(jetzig.jetquery.config.database, @tagName(tag)).database,
|
inline else => |tag| vars.get("JETQUERY_DATABASE") orelse
|
||||||
|
@field(jetzig.jetquery.config.database, @tagName(tag)).database,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -216,7 +285,7 @@ pub fn init(parent_allocator: std.mem.Allocator) !Environment {
|
|||||||
.bind = try allocator.dupe(u8, options.options.bind),
|
.bind = try allocator.dupe(u8, options.options.bind),
|
||||||
.port = options.options.port,
|
.port = options.options.port,
|
||||||
.detach = options.options.detach,
|
.detach = options.options.detach,
|
||||||
.environment = environment,
|
.environment = jetzig.environment,
|
||||||
.vars = vars,
|
.vars = vars,
|
||||||
.log_queue = log_queue,
|
.log_queue = log_queue,
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,11 @@ const std = @import("std");
|
|||||||
|
|
||||||
const jetzig = @import("../jetzig.zig");
|
const jetzig = @import("../jetzig.zig");
|
||||||
|
|
||||||
pub const adapter = @field(jetzig.jetquery.config.database, @tagName(jetzig.environment)).adapter;
|
pub const adapter = std.enums.nameCast(
|
||||||
|
jetzig.jetquery.adapters.Name,
|
||||||
|
@field(jetzig.jetquery.config.database, @tagName(jetzig.environment)).adapter,
|
||||||
|
);
|
||||||
|
|
||||||
pub const Schema = jetzig.config.get(type, "Schema");
|
pub const Schema = jetzig.config.get(type, "Schema");
|
||||||
pub const Repo = jetzig.jetquery.Repo(adapter, Schema);
|
pub const Repo = jetzig.jetquery.Repo(adapter, Schema);
|
||||||
|
|
||||||
@ -11,7 +15,6 @@ pub fn Query(comptime model: anytype) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn repo(allocator: std.mem.Allocator, app: anytype) !Repo {
|
pub fn repo(allocator: std.mem.Allocator, app: anytype) !Repo {
|
||||||
// XXX: Is this terrible ?
|
|
||||||
const Callback = struct {
|
const Callback = struct {
|
||||||
var jetzig_app: @TypeOf(app) = undefined;
|
var jetzig_app: @TypeOf(app) = undefined;
|
||||||
pub fn callbackFn(event: jetzig.jetquery.events.Event) !void {
|
pub fn callbackFn(event: jetzig.jetquery.events.Event) !void {
|
||||||
@ -25,7 +28,12 @@ pub fn repo(allocator: std.mem.Allocator, app: anytype) !Repo {
|
|||||||
std.enums.nameCast(jetzig.jetquery.Environment, jetzig.environment),
|
std.enums.nameCast(jetzig.jetquery.Environment, jetzig.environment),
|
||||||
.{
|
.{
|
||||||
.eventCallback = Callback.callbackFn,
|
.eventCallback = Callback.callbackFn,
|
||||||
.lazy_connect = jetzig.environment == .development,
|
.lazy_connect = switch (jetzig.environment) {
|
||||||
|
.development, .production => true,
|
||||||
|
.testing => false,
|
||||||
|
},
|
||||||
|
// Checking field presence here makes setting up test App a bit simpler.
|
||||||
|
.env = if (@hasField(@TypeOf(app), "env")) try repoEnv(app.env) else .{},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -36,3 +44,16 @@ fn eventCallback(event: jetzig.jetquery.events.Event, app: anytype) !void {
|
|||||||
try app.server.logger.ERROR("[database] {?s}", .{err.message});
|
try app.server.logger.ERROR("[database] {?s}", .{err.message});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn repoEnv(env: jetzig.Environment) !Repo.AdapterOptions {
|
||||||
|
return switch (comptime adapter) {
|
||||||
|
.null => .{},
|
||||||
|
.postgresql => .{
|
||||||
|
.hostname = @as(?[]const u8, env.vars.get("JETQUERY_HOSTNAME")),
|
||||||
|
.port = @as(?u16, try env.vars.getT(u16, "JETQUERY_PORT")),
|
||||||
|
.username = @as(?[]const u8, env.vars.get("JETQUERY_USERNAME")),
|
||||||
|
.password = @as(?[]const u8, env.vars.get("JETQUERY_PASSWORD")),
|
||||||
|
.database = @as(?[]const u8, env.vars.get("JETQUERY_DATABASE")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -7,10 +7,12 @@ const Self = @This();
|
|||||||
pub const DevelopmentLogger = @import("loggers/DevelopmentLogger.zig");
|
pub const DevelopmentLogger = @import("loggers/DevelopmentLogger.zig");
|
||||||
pub const JsonLogger = @import("loggers/JsonLogger.zig");
|
pub const JsonLogger = @import("loggers/JsonLogger.zig");
|
||||||
pub const TestLogger = @import("loggers/TestLogger.zig");
|
pub const TestLogger = @import("loggers/TestLogger.zig");
|
||||||
|
pub const ProductionLogger = @import("loggers/ProductionLogger.zig");
|
||||||
|
|
||||||
pub const LogQueue = @import("loggers/LogQueue.zig");
|
pub const LogQueue = @import("loggers/LogQueue.zig");
|
||||||
|
|
||||||
pub const LogLevel = enum(u4) { TRACE, DEBUG, INFO, WARN, ERROR, FATAL };
|
pub const LogLevel = enum(u4) { TRACE, DEBUG, INFO, WARN, ERROR, FATAL };
|
||||||
pub const LogFormat = enum { development, json };
|
pub const LogFormat = enum { development, production, json };
|
||||||
|
|
||||||
/// Infer a log target (stdout or stderr) from a given log level.
|
/// Infer a log target (stdout or stderr) from a given log level.
|
||||||
pub inline fn logTarget(comptime level: LogLevel) LogQueue.Target {
|
pub inline fn logTarget(comptime level: LogLevel) LogQueue.Target {
|
||||||
@ -23,6 +25,7 @@ pub const Logger = union(enum) {
|
|||||||
development_logger: DevelopmentLogger,
|
development_logger: DevelopmentLogger,
|
||||||
json_logger: JsonLogger,
|
json_logger: JsonLogger,
|
||||||
test_logger: TestLogger,
|
test_logger: TestLogger,
|
||||||
|
production_logger: ProductionLogger,
|
||||||
|
|
||||||
/// Log a TRACE level message to the configured logger.
|
/// Log a TRACE level message to the configured logger.
|
||||||
pub fn TRACE(self: *const Logger, comptime message: []const u8, args: anytype) !void {
|
pub fn TRACE(self: *const Logger, comptime message: []const u8, args: anytype) !void {
|
||||||
|
@ -8,25 +8,30 @@ const Timestamp = jetzig.types.Timestamp;
|
|||||||
const LogLevel = jetzig.loggers.LogLevel;
|
const LogLevel = jetzig.loggers.LogLevel;
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
stdout: std.fs.File,
|
||||||
|
stderr: std.fs.File,
|
||||||
stdout_colorized: bool,
|
stdout_colorized: bool,
|
||||||
stderr_colorized: bool,
|
stderr_colorized: bool,
|
||||||
level: LogLevel,
|
level: LogLevel,
|
||||||
log_queue: *jetzig.loggers.LogQueue,
|
|
||||||
mutex: *std.Thread.Mutex,
|
mutex: *std.Thread.Mutex,
|
||||||
|
|
||||||
/// Initialize a new Development Logger.
|
/// Initialize a new Development Logger.
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
level: LogLevel,
|
level: LogLevel,
|
||||||
log_queue: *jetzig.loggers.LogQueue,
|
stdout: std.fs.File,
|
||||||
|
stderr: std.fs.File,
|
||||||
) DevelopmentLogger {
|
) DevelopmentLogger {
|
||||||
const mutex = allocator.create(std.Thread.Mutex) catch unreachable;
|
const mutex = allocator.create(std.Thread.Mutex) catch unreachable;
|
||||||
|
mutex.* = std.Thread.Mutex{};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.level = level,
|
.level = level,
|
||||||
.log_queue = log_queue,
|
.stdout = stdout,
|
||||||
.stdout_colorized = log_queue.stdout_is_tty,
|
.stderr = stderr,
|
||||||
.stderr_colorized = log_queue.stderr_is_tty,
|
.stdout_colorized = stdout.isTty(),
|
||||||
|
.stderr_colorized = stderr.isTty(),
|
||||||
.mutex = mutex,
|
.mutex = mutex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -38,6 +43,9 @@ pub fn log(
|
|||||||
comptime message: []const u8,
|
comptime message: []const u8,
|
||||||
args: anytype,
|
args: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
|
||||||
if (@intFromEnum(level) < @intFromEnum(self.level)) return;
|
if (@intFromEnum(level) < @intFromEnum(self.level)) return;
|
||||||
|
|
||||||
const output = try std.fmt.allocPrint(self.allocator, message, args);
|
const output = try std.fmt.allocPrint(self.allocator, message, args);
|
||||||
@ -47,13 +55,11 @@ pub fn log(
|
|||||||
var timestamp_buf: [256]u8 = undefined;
|
var timestamp_buf: [256]u8 = undefined;
|
||||||
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
||||||
|
|
||||||
const target = jetzig.loggers.logTarget(level);
|
|
||||||
const formatted_level = colorizedLogLevel(level);
|
const formatted_level = colorizedLogLevel(level);
|
||||||
|
|
||||||
try self.log_queue.print(
|
try self.logWriter(level).print(
|
||||||
"{s: >5} [{s}] {s}\n",
|
"{s: >5} [{s}] {s}\n",
|
||||||
.{ formatted_level, iso8601, output },
|
.{ formatted_level, iso8601, output },
|
||||||
target,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +93,7 @@ pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request)
|
|||||||
|
|
||||||
const formatted_level = if (self.stdout_colorized) colorizedLogLevel(.INFO) else @tagName(.INFO);
|
const formatted_level = if (self.stdout_colorized) colorizedLogLevel(.INFO) else @tagName(.INFO);
|
||||||
|
|
||||||
try self.log_queue.print("{s: >5} [{s}] [{s}/{s}/{s}]{s}{s}{s}{s}{s}{s}{s}{s}{s}{s} {s}\n", .{
|
try self.logWriter(.INFO).print("{s: >5} [{s}] [{s}/{s}/{s}]{s}{s}{s}{s}{s}{s}{s}{s}{s}{s} {s}\n", .{
|
||||||
formatted_level,
|
formatted_level,
|
||||||
iso8601,
|
iso8601,
|
||||||
formatted_duration,
|
formatted_duration,
|
||||||
@ -104,22 +110,15 @@ pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request)
|
|||||||
if (request.middleware_rendered) |_| jetzig.colors.codes.escape ++ jetzig.colors.codes.reset else "",
|
if (request.middleware_rendered) |_| jetzig.colors.codes.escape ++ jetzig.colors.codes.reset else "",
|
||||||
if (request.middleware_rendered) |_| "]" else "",
|
if (request.middleware_rendered) |_| "]" else "",
|
||||||
request.path.path,
|
request.path.path,
|
||||||
}, .stdout);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logSql(self: *const DevelopmentLogger, event: jetzig.jetquery.events.Event) !void {
|
pub fn logSql(self: *const DevelopmentLogger, event: jetzig.jetquery.events.Event) !void {
|
||||||
self.mutex.lock();
|
|
||||||
defer self.mutex.unlock();
|
|
||||||
|
|
||||||
// XXX: This function does not make any effort to prevent log messages clobbering each other
|
// XXX: This function does not make any effort to prevent log messages clobbering each other
|
||||||
// from multiple threads. JSON logger etc. write in one call and the logger's mutex prevents
|
// from multiple threads. JSON logger etc. write in one call and the log queue prevents
|
||||||
// clobbering, but this is not the case here.
|
// clobbering, but this is not the case here.
|
||||||
const formatted_level = if (self.stdout_colorized) colorizedLogLevel(.INFO) else @tagName(.INFO);
|
const formatted_level = if (self.stdout_colorized) colorizedLogLevel(.INFO) else @tagName(.INFO);
|
||||||
try self.log_queue.print(
|
try self.logWriter(.INFO).print("{s} [database] ", .{formatted_level});
|
||||||
"{s} [database] ",
|
|
||||||
.{formatted_level},
|
|
||||||
.stdout,
|
|
||||||
);
|
|
||||||
try self.printSql(event.sql orelse "");
|
try self.printSql(event.sql orelse "");
|
||||||
|
|
||||||
var duration_buf: [256]u8 = undefined;
|
var duration_buf: [256]u8 = undefined;
|
||||||
@ -129,10 +128,9 @@ pub fn logSql(self: *const DevelopmentLogger, event: jetzig.jetquery.events.Even
|
|||||||
self.stdout_colorized,
|
self.stdout_colorized,
|
||||||
) else "";
|
) else "";
|
||||||
|
|
||||||
try self.log_queue.print(
|
try self.logWriter(.INFO).print(
|
||||||
std.fmt.comptimePrint(" [{s}]\n", .{jetzig.colors.cyan("{s}")}),
|
std.fmt.comptimePrint(" [{s}]\n", .{jetzig.colors.cyan("{s}")}),
|
||||||
.{formatted_duration},
|
.{formatted_duration},
|
||||||
.stdout,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +168,9 @@ fn printSql(self: *const DevelopmentLogger, sql: []const u8) !void {
|
|||||||
const string_color = jetzig.colors.codes.escape ++ jetzig.colors.codes.green;
|
const string_color = jetzig.colors.codes.escape ++ jetzig.colors.codes.green;
|
||||||
const identifier_color = jetzig.colors.codes.escape ++ jetzig.colors.codes.yellow;
|
const identifier_color = jetzig.colors.codes.escape ++ jetzig.colors.codes.yellow;
|
||||||
const reset_color = jetzig.colors.codes.escape ++ jetzig.colors.codes.reset;
|
const reset_color = jetzig.colors.codes.escape ++ jetzig.colors.codes.reset;
|
||||||
|
var buf: [4096]u8 = undefined;
|
||||||
|
var stream = std.io.fixedBufferStream(&buf);
|
||||||
|
const writer = stream.writer();
|
||||||
|
|
||||||
var index: usize = 0;
|
var index: usize = 0;
|
||||||
var single_quote: bool = false;
|
var single_quote: bool = false;
|
||||||
@ -181,9 +182,9 @@ fn printSql(self: *const DevelopmentLogger, sql: []const u8) !void {
|
|||||||
if (!single_quote) {
|
if (!single_quote) {
|
||||||
double_quote = !double_quote;
|
double_quote = !double_quote;
|
||||||
if (double_quote) {
|
if (double_quote) {
|
||||||
try self.log_queue.print(identifier_color ++ "\"", .{}, .stdout);
|
try writer.print(identifier_color ++ "\"", .{});
|
||||||
} else {
|
} else {
|
||||||
try self.log_queue.print("\"" ++ reset_color, .{}, .stdout);
|
try writer.print("\"" ++ reset_color, .{});
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
@ -192,16 +193,16 @@ fn printSql(self: *const DevelopmentLogger, sql: []const u8) !void {
|
|||||||
if (!double_quote) {
|
if (!double_quote) {
|
||||||
single_quote = !single_quote;
|
single_quote = !single_quote;
|
||||||
if (single_quote) {
|
if (single_quote) {
|
||||||
try self.log_queue.print(string_color ++ "'", .{}, .stdout);
|
try writer.print(string_color ++ "'", .{});
|
||||||
} else {
|
} else {
|
||||||
try self.log_queue.print("'" ++ reset_color, .{}, .stdout);
|
try writer.print("'" ++ reset_color, .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
},
|
},
|
||||||
'$' => {
|
'$' => {
|
||||||
if (double_quote or single_quote) {
|
if (double_quote or single_quote) {
|
||||||
try self.log_queue.print("{c}", .{sql[index]}, .stdout);
|
try writer.print("{c}", .{sql[index]});
|
||||||
index += 1;
|
index += 1;
|
||||||
} else {
|
} else {
|
||||||
const param = sql[index..][0 .. std.mem.indexOfAny(
|
const param = sql[index..][0 .. std.mem.indexOfAny(
|
||||||
@ -209,29 +210,30 @@ fn printSql(self: *const DevelopmentLogger, sql: []const u8) !void {
|
|||||||
sql[index..],
|
sql[index..],
|
||||||
&std.ascii.whitespace,
|
&std.ascii.whitespace,
|
||||||
) orelse sql.len - index];
|
) orelse sql.len - index];
|
||||||
try self.log_queue.print(jetzig.colors.magenta("{s}"), .{param}, .stdout);
|
try writer.print(jetzig.colors.magenta("{s}"), .{param});
|
||||||
index += param.len;
|
index += param.len;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
if (double_quote or single_quote) {
|
if (double_quote or single_quote) {
|
||||||
try self.log_queue.print("{c}", .{sql[index]}, .stdout);
|
try writer.print("{c}", .{sql[index]});
|
||||||
index += 1;
|
index += 1;
|
||||||
} else {
|
} else {
|
||||||
inline for (sql_tokens) |token| {
|
inline for (sql_tokens) |token| {
|
||||||
if (std.mem.startsWith(u8, sql[index..], token)) {
|
if (std.mem.startsWith(u8, sql[index..], token)) {
|
||||||
try self.log_queue.print(jetzig.colors.cyan(token), .{}, .stdout);
|
try writer.print(jetzig.colors.cyan(token), .{});
|
||||||
index += token.len;
|
index += token.len;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try self.log_queue.print("{c}", .{sql[index]}, .stdout);
|
try writer.print("{c}", .{sql[index]});
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try self.logWriter(.INFO).print("{s}", .{stream.getWritten()});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logError(self: *const DevelopmentLogger, err: anyerror) !void {
|
pub fn logError(self: *const DevelopmentLogger, err: anyerror) !void {
|
||||||
@ -247,6 +249,14 @@ pub fn logError(self: *const DevelopmentLogger, err: anyerror) !void {
|
|||||||
try self.log(.ERROR, "Encountered Error: {s}", .{@errorName(err)});
|
try self.log(.ERROR, "Encountered Error: {s}", .{@errorName(err)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn logWriter(self: DevelopmentLogger, comptime level: jetzig.loggers.LogLevel) std.fs.File.Writer {
|
||||||
|
const target = comptime jetzig.loggers.logTarget(level);
|
||||||
|
return switch (target) {
|
||||||
|
.stdout => self.stdout,
|
||||||
|
.stderr => self.stderr,
|
||||||
|
}.writer();
|
||||||
|
}
|
||||||
|
|
||||||
inline fn colorizedLogLevel(comptime level: LogLevel) []const u8 {
|
inline fn colorizedLogLevel(comptime level: LogLevel) []const u8 {
|
||||||
return switch (level) {
|
return switch (level) {
|
||||||
.TRACE => jetzig.colors.white(@tagName(level)),
|
.TRACE => jetzig.colors.white(@tagName(level)),
|
||||||
|
146
src/jetzig/loggers/ProductionLogger.zig
Normal file
146
src/jetzig/loggers/ProductionLogger.zig
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
|
const ProductionLogger = @This();
|
||||||
|
|
||||||
|
const Timestamp = jetzig.types.Timestamp;
|
||||||
|
const LogLevel = jetzig.loggers.LogLevel;
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
level: LogLevel,
|
||||||
|
log_queue: *jetzig.loggers.LogQueue,
|
||||||
|
|
||||||
|
/// Initialize a new Development Logger.
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
level: LogLevel,
|
||||||
|
log_queue: *jetzig.loggers.LogQueue,
|
||||||
|
) ProductionLogger {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.level = level,
|
||||||
|
.log_queue = log_queue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic log function, receives log level, message (format string), and args for format string.
|
||||||
|
pub fn log(
|
||||||
|
self: *const ProductionLogger,
|
||||||
|
comptime level: LogLevel,
|
||||||
|
comptime message: []const u8,
|
||||||
|
args: anytype,
|
||||||
|
) !void {
|
||||||
|
if (@intFromEnum(level) < @intFromEnum(self.level)) return;
|
||||||
|
|
||||||
|
const output = try std.fmt.allocPrint(self.allocator, message, args);
|
||||||
|
defer self.allocator.free(output);
|
||||||
|
|
||||||
|
const timestamp = Timestamp.init(std.time.timestamp());
|
||||||
|
var timestamp_buf: [256]u8 = undefined;
|
||||||
|
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
||||||
|
|
||||||
|
const target = jetzig.loggers.logTarget(level);
|
||||||
|
|
||||||
|
try self.log_queue.print(
|
||||||
|
"{s} [{s}] {s}\n",
|
||||||
|
.{ @tagName(level), iso8601, output },
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log a one-liner including response status code, path, method, duration, etc.
|
||||||
|
pub fn logRequest(self: ProductionLogger, request: *const jetzig.http.Request) !void {
|
||||||
|
if (@intFromEnum(LogLevel.INFO) < @intFromEnum(self.level)) return;
|
||||||
|
|
||||||
|
var duration_buf: [256]u8 = undefined;
|
||||||
|
const formatted_duration = try jetzig.colors.duration(
|
||||||
|
&duration_buf,
|
||||||
|
jetzig.util.duration(request.start_time),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const status: jetzig.http.status_codes.TaggedStatusCode = switch (request.response.status_code) {
|
||||||
|
inline else => |status_code| @unionInit(
|
||||||
|
jetzig.http.status_codes.TaggedStatusCode,
|
||||||
|
@tagName(status_code),
|
||||||
|
.{},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatted_status = status.getFormatted(.{});
|
||||||
|
const timestamp = Timestamp.init(std.time.timestamp());
|
||||||
|
var timestamp_buf: [256]u8 = undefined;
|
||||||
|
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
||||||
|
|
||||||
|
const formatted_level = @tagName(.INFO);
|
||||||
|
|
||||||
|
try self.log_queue.print("{s} [{s}] [{s}/{s}/{s}]{s}{s}{s}{s}{s}{s}{s} {s}\n", .{
|
||||||
|
formatted_level,
|
||||||
|
iso8601,
|
||||||
|
formatted_duration,
|
||||||
|
request.fmtMethod(false),
|
||||||
|
formatted_status,
|
||||||
|
if (request.middleware_rendered) |_| " [" else "",
|
||||||
|
if (request.middleware_rendered) |middleware| middleware.name else "",
|
||||||
|
if (request.middleware_rendered) |_| ":" else "",
|
||||||
|
if (request.middleware_rendered) |middleware| middleware.action else "",
|
||||||
|
if (request.middleware_rendered) |_| ":" else "",
|
||||||
|
if (request.middleware_rendered) |_| if (request.redirected) "redirect" else "render" else "",
|
||||||
|
if (request.middleware_rendered) |_| "]" else "",
|
||||||
|
request.path.path,
|
||||||
|
}, .stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logSql(self: *const ProductionLogger, event: jetzig.jetquery.events.Event) !void {
|
||||||
|
var duration_buf: [256]u8 = undefined;
|
||||||
|
const formatted_duration = if (event.duration) |duration| try jetzig.colors.duration(
|
||||||
|
&duration_buf,
|
||||||
|
duration,
|
||||||
|
false,
|
||||||
|
) else "";
|
||||||
|
|
||||||
|
const timestamp = Timestamp.init(std.time.timestamp());
|
||||||
|
var timestamp_buf: [256]u8 = undefined;
|
||||||
|
const iso8601 = try timestamp.iso8601(×tamp_buf);
|
||||||
|
|
||||||
|
try self.log_queue.print(
|
||||||
|
"{s} [{s}] [database] [sql:{s}] [duration:{s}]\n",
|
||||||
|
.{ @tagName(.INFO), iso8601, event.sql orelse "", formatted_duration },
|
||||||
|
.stdout,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sql_tokens = .{
|
||||||
|
"SELECT",
|
||||||
|
"INSERT",
|
||||||
|
"UPDATE",
|
||||||
|
"DELETE",
|
||||||
|
"WHERE",
|
||||||
|
"SET",
|
||||||
|
"ANY",
|
||||||
|
"FROM",
|
||||||
|
"INTO",
|
||||||
|
"IN",
|
||||||
|
"ON",
|
||||||
|
"IS",
|
||||||
|
"NOT",
|
||||||
|
"NULL",
|
||||||
|
"LIMIT",
|
||||||
|
"ORDER BY",
|
||||||
|
"GROUP BY",
|
||||||
|
"HAVING",
|
||||||
|
"LEFT OUTER JOIN",
|
||||||
|
"INNER JOIN",
|
||||||
|
"ASC",
|
||||||
|
"DESC",
|
||||||
|
"MAX",
|
||||||
|
"MIN",
|
||||||
|
"COUNT",
|
||||||
|
"SUM",
|
||||||
|
"VALUES",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn logError(self: *const ProductionLogger, err: anyerror) !void {
|
||||||
|
try self.log(.ERROR, "Encountered Error: {s}", .{@errorName(err)});
|
||||||
|
}
|
@ -361,7 +361,7 @@ test "environment SMTP config" {
|
|||||||
try env_map.put("JETZIG_SMTP_USERNAME", "example-username");
|
try env_map.put("JETZIG_SMTP_USERNAME", "example-username");
|
||||||
try env_map.put("JETZIG_SMTP_PASSWORD", "example-password");
|
try env_map.put("JETZIG_SMTP_PASSWORD", "example-password");
|
||||||
|
|
||||||
env.vars = jetzig.Environment.Vars{ .env_map = env_map };
|
env.vars = jetzig.Environment.Vars{ .env_map = env_map, .env_file = null };
|
||||||
|
|
||||||
const mail = Mail{
|
const mail = Mail{
|
||||||
.allocator = undefined,
|
.allocator = undefined,
|
||||||
|
@ -114,7 +114,10 @@ pub fn request(
|
|||||||
// We init the `std.process.EnvMap` directly here (instead of calling `std.process.getEnvMap`
|
// We init the `std.process.EnvMap` directly here (instead of calling `std.process.getEnvMap`
|
||||||
// to ensure that tests run in a clean environment. Users can manually add items to the
|
// to ensure that tests run in a clean environment. Users can manually add items to the
|
||||||
// environment within a test if required.
|
// environment within a test if required.
|
||||||
const vars = jetzig.Environment.Vars{ .env_map = std.process.EnvMap.init(allocator) };
|
const vars = jetzig.Environment.Vars{
|
||||||
|
.env_map = std.process.EnvMap.init(allocator),
|
||||||
|
.env_file = null,
|
||||||
|
};
|
||||||
var server = jetzig.http.Server{
|
var server = jetzig.http.Server{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.logger = self.logger,
|
.logger = self.logger,
|
||||||
|
@ -57,6 +57,15 @@ pub inline fn strip(input: []const u8) []const u8 {
|
|||||||
return std.mem.trim(u8, input, &std.ascii.whitespace);
|
return std.mem.trim(u8, input, &std.ascii.whitespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub inline fn unquote(input: []const u8) []const u8 {
|
||||||
|
return if (std.mem.startsWith(u8, input, "\"") and std.mem.endsWith(u8, input, "\""))
|
||||||
|
std.mem.trim(u8, input, "\"")
|
||||||
|
else if (std.mem.startsWith(u8, input, "'") and std.mem.endsWith(u8, input, "'"))
|
||||||
|
std.mem.trim(u8, input, "'")
|
||||||
|
else
|
||||||
|
input;
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate a secure random string of `len` characters (for cryptographic purposes).
|
/// Generate a secure random string of `len` characters (for cryptographic purposes).
|
||||||
pub fn generateSecret(allocator: std.mem.Allocator, comptime len: u10) ![]const u8 {
|
pub fn generateSecret(allocator: std.mem.Allocator, comptime len: u10) ![]const u8 {
|
||||||
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user