Skip to content

Commit 3507fbe

Browse files
committed
feat: add jslogger
1 parent 92df955 commit 3507fbe

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed

cpp/HybridStyleRegistry.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "Environment.hpp"
88
#include "VariableContext.hpp"
99
#include "PseudoClasses.hpp"
10+
#include "JSLogger.hpp"
1011

1112
#include <regex>
1213
#include <string>
@@ -278,6 +279,7 @@ namespace margelo::nitro::cssnitro {
278279
HybridStyleRegistry::linkComponent(jsi::Runtime &runtime, const jsi::Value &thisValue,
279280
const jsi::Value *args, size_t count) {
280281
(void) thisValue;
282+
281283
if (count < 2) {
282284
return jsi::Value::undefined();
283285
}
@@ -298,6 +300,14 @@ namespace margelo::nitro::cssnitro {
298300
const jsi::Value *args, size_t count) {
299301
(void) thisValue;
300302

303+
// Initialize JSLogger on first call with runtime access
304+
static bool jsLoggerInitialized = false;
305+
if (!jsLoggerInitialized) {
306+
JSLogger::initialize(runtime);
307+
jsLoggerInitialized = true;
308+
309+
}
310+
301311
if (!args[0].isObject()) {
302312
return jsi::Value::undefined();
303313
}
@@ -307,6 +317,7 @@ namespace margelo::nitro::cssnitro {
307317
jsi::Function processColorFn = maybeProcessColorFn.asObject(runtime).asFunction(runtime);
308318

309319
shadowUpdates_->registerProcessColorFunction(std::move(processColorFn));
320+
JSLOGD("processColor was registered in HybridStyleRegistry");
310321

311322
return jsi::Value::undefined();
312323
}

cpp/JSLogger.hpp

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <sstream>
5+
#include <jsi/jsi.h>
6+
#include <memory>
7+
#include <mutex>
8+
9+
// Platform-specific includes for detecting debug build
10+
#ifdef __ANDROID__
11+
12+
#include <fbjni/fbjni.h>
13+
14+
#endif
15+
16+
namespace margelo::nitro::cssnitro {
17+
18+
/**
19+
* JSLogger - Bridges C++ logs to JavaScript console (React Native Dev Tools)
20+
*
21+
* This logger sends logs to the JavaScript console.log/warn/error functions,
22+
* making them visible in React Native Dev Tools, Metro bundler, and Flipper.
23+
*
24+
* Note: Requires initialization with a JSI runtime reference.
25+
*/
26+
class JSLogger {
27+
public:
28+
enum class Level {
29+
Log,
30+
Debug,
31+
Info,
32+
Warn,
33+
Error
34+
};
35+
36+
/**
37+
* Initialize the JSLogger with a JSI runtime.
38+
* Call this once during module initialization.
39+
*/
40+
static void initialize(facebook::jsi::Runtime &runtime) {
41+
std::lock_guard<std::mutex> lock(mutex_);
42+
runtime_ = &runtime;
43+
44+
// Cache the console object and its methods
45+
auto console = runtime.global().getPropertyAsObject(runtime, "console");
46+
console_log_ = std::make_shared<facebook::jsi::Function>(
47+
console.getPropertyAsFunction(runtime, "log"));
48+
console_debug_ = std::make_shared<facebook::jsi::Function>(
49+
console.getPropertyAsFunction(runtime, "debug"));
50+
console_info_ = std::make_shared<facebook::jsi::Function>(
51+
console.getPropertyAsFunction(runtime, "info"));
52+
console_warn_ = std::make_shared<facebook::jsi::Function>(
53+
console.getPropertyAsFunction(runtime, "warn"));
54+
console_error_ = std::make_shared<facebook::jsi::Function>(
55+
console.getPropertyAsFunction(runtime, "error"));
56+
}
57+
58+
/**
59+
* Check if running in a debuggable build (Android Studio Debug button)
60+
*/
61+
static bool isDebuggableBuild() {
62+
#ifdef __ANDROID__
63+
static bool checked = false;
64+
static bool isDebuggable = false;
65+
66+
if (!checked) {
67+
try {
68+
// Use android.os.Debug.isDebuggerConnected() to check if debugger is attached
69+
auto debugClass = facebook::jni::findClassLocal("android/os/Debug");
70+
auto isDebuggerConnectedMethod = debugClass->getStaticMethod<jboolean()>(
71+
"isDebuggerConnected");
72+
isDebuggable = isDebuggerConnectedMethod(debugClass);
73+
} catch (const std::exception &e) {
74+
// If we can't check, default to true (always show logs in dev)
75+
isDebuggable = true;
76+
} catch (...) {
77+
isDebuggable = true;
78+
}
79+
checked = true;
80+
}
81+
82+
return isDebuggable;
83+
#elif defined(__APPLE__)
84+
// On iOS, check if debugger is attached
85+
#ifdef DEBUG
86+
return true;
87+
#else
88+
return false;
89+
#endif
90+
#else
91+
// Other platforms - fall back to compile-time check
92+
#ifdef NDEBUG
93+
return false;
94+
#else
95+
return true;
96+
#endif
97+
#endif
98+
}
99+
100+
/**
101+
* Enable or disable debug logging at runtime
102+
*/
103+
static void setDebugMode(bool enabled) {
104+
std::lock_guard<std::mutex> lock(mutex_);
105+
debugModeOverride_ = enabled;
106+
hasOverride_ = true;
107+
}
108+
109+
/**
110+
* Check if debug mode is enabled
111+
*/
112+
static bool isDebugMode() {
113+
std::lock_guard<std::mutex> lock(mutex_);
114+
// If there's a manual override, use that
115+
if (hasOverride_) {
116+
return debugModeOverride_;
117+
}
118+
// Otherwise, check if it's a debuggable build
119+
return isDebuggableBuild();
120+
}
121+
122+
/**
123+
* Log a message to JavaScript console
124+
*/
125+
static void log(Level level, const std::string &message) {
126+
std::lock_guard<std::mutex> lock(mutex_);
127+
128+
if (!runtime_ || !console_log_) {
129+
return; // Not initialized yet
130+
}
131+
132+
try {
133+
auto &rt = *runtime_;
134+
auto jsiMsg = facebook::jsi::String::createFromUtf8(rt, message);
135+
136+
switch (level) {
137+
case Level::Log:
138+
console_log_->call(rt, jsiMsg);
139+
break;
140+
case Level::Debug:
141+
if (console_debug_) {
142+
console_debug_->call(rt, jsiMsg);
143+
} else {
144+
console_log_->call(rt, jsiMsg);
145+
}
146+
break;
147+
case Level::Info:
148+
if (console_info_) {
149+
console_info_->call(rt, jsiMsg);
150+
} else {
151+
console_log_->call(rt, jsiMsg);
152+
}
153+
break;
154+
case Level::Warn:
155+
if (console_warn_) {
156+
console_warn_->call(rt, jsiMsg);
157+
} else {
158+
console_log_->call(rt, jsiMsg);
159+
}
160+
break;
161+
case Level::Error:
162+
if (console_error_) {
163+
console_error_->call(rt, jsiMsg);
164+
} else {
165+
console_log_->call(rt, jsiMsg);
166+
}
167+
break;
168+
}
169+
} catch (...) {
170+
// Silently fail if JS call fails (e.g., runtime destroyed)
171+
}
172+
}
173+
174+
// Convenience methods
175+
static void log(const std::string &message) {
176+
log(Level::Log, message);
177+
}
178+
179+
static void debug(const std::string &message) {
180+
log(Level::Debug, message);
181+
}
182+
183+
static void info(const std::string &message) {
184+
log(Level::Info, message);
185+
}
186+
187+
static void warn(const std::string &message) {
188+
log(Level::Warn, message);
189+
}
190+
191+
static void error(const std::string &message) {
192+
log(Level::Error, message);
193+
}
194+
195+
// Helper to format multiple arguments into a single string
196+
template<typename T>
197+
static std::string format(T &&value) {
198+
std::ostringstream oss;
199+
oss << std::forward<T>(value);
200+
return oss.str();
201+
}
202+
203+
template<typename T, typename... Args>
204+
static std::string format(T &&first, Args &&... rest) {
205+
std::ostringstream oss;
206+
oss << std::forward<T>(first);
207+
((oss << " " << std::forward<Args>(rest)), ...);
208+
return oss.str();
209+
}
210+
211+
private:
212+
static inline std::mutex mutex_;
213+
static inline facebook::jsi::Runtime *runtime_ = nullptr;
214+
static inline std::shared_ptr<facebook::jsi::Function> console_log_;
215+
static inline std::shared_ptr<facebook::jsi::Function> console_debug_;
216+
static inline std::shared_ptr<facebook::jsi::Function> console_info_;
217+
static inline std::shared_ptr<facebook::jsi::Function> console_warn_;
218+
static inline std::shared_ptr<facebook::jsi::Function> console_error_;
219+
220+
// Manual override for debug mode
221+
static inline bool debugModeOverride_ = false;
222+
static inline bool hasOverride_ = false;
223+
};
224+
225+
} // namespace margelo::nitro::cssnitro
226+
227+
// Production macros - always log (Warnings and Errors should always be visible)
228+
#define JSLOGW(...) margelo::nitro::cssnitro::JSLogger::warn(margelo::nitro::cssnitro::JSLogger::format(__VA_ARGS__))
229+
#define JSLOGE(...) margelo::nitro::cssnitro::JSLogger::error(margelo::nitro::cssnitro::JSLogger::format(__VA_ARGS__))
230+
231+
// Debug-only macros - only log when built with Android Studio's Debug button
232+
#define JSLOGD(...) do { \
233+
if (margelo::nitro::cssnitro::JSLogger::isDebugMode()) { \
234+
margelo::nitro::cssnitro::JSLogger::debug(margelo::nitro::cssnitro::JSLogger::format(__VA_ARGS__)); \
235+
} \
236+
} while(0)
237+
238+
#define JSLOGI(...) do { \
239+
if (margelo::nitro::cssnitro::JSLogger::isDebugMode()) { \
240+
margelo::nitro::cssnitro::JSLogger::info(margelo::nitro::cssnitro::JSLogger::format(__VA_ARGS__)); \
241+
} \
242+
} while(0)
243+
244+
// Force debug log (always logs even in non-debuggable builds - use sparingly!)
245+
#define JSLOGD_FORCE(...) margelo::nitro::cssnitro::JSLogger::debug(margelo::nitro::cssnitro::JSLogger::format(__VA_ARGS__))
246+
#define JSLOGI_FORCE(...) margelo::nitro::cssnitro::JSLogger::info(margelo::nitro::cssnitro::JSLogger::format(__VA_ARGS__))

0 commit comments

Comments
 (0)