Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ clippy-fix:

.PHONY: fmt
fmt:
${MAYBE_ENVIRONMENT_EXEC} $(VDEV) fmt
cargo fmt --all

.PHONY: build-licenses
build-licenses:
Expand Down
126 changes: 95 additions & 31 deletions src/transforms/detect_exceptions/exception_detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ mod exception_detector_tests {
check_exception(java_simple_exception(), false);
check_exception(java_complex_exception(), false);
check_exception(java_nested_exception(), false);
check_exception(java_exception_with_non_indented_continuation(), false);
}

const fn java_simple_exception() -> &'static str {
Expand Down Expand Up @@ -366,6 +367,18 @@ com.sun.mail.smtp.SMTPAddressFailedException: 550 5.7.1 <[REDACTED_EMAIL_ADDRESS
at com.nethunt.crm.api.server.adminsync.AutomaticEmailFacade.sendWithSmtp(AutomaticEmailFacade.java:229)
... 12 more
Caused by: com.sun.mail.smtp.SMTPAddressFailedException: 550 5.7.1 <[REDACTED_EMAIL_ADDRESS]>... Relaying denied
at com.sun.mail.smtp.SMTPTransport.rcptTo(SMTPTransport.java:2064)
... 12 more
"
}

const fn java_exception_with_non_indented_continuation() -> &'static str {
"
java.sql.SQLException: Listener refused the connection with the following error:
ORA-12521, TNS:listener does not currently know of instance requested in connect descriptor
(CONNECTION_ID=r6n2ZPL0TqS/BLDhIydj+A==)
at oracle.jdbc.driver.T4CConnection.handleLogonNetException(T4CConnection.java:893)
at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:698)
"
}

Expand Down Expand Up @@ -563,37 +576,33 @@ created by net/http.(*Server).Serve
"
}

fn rails_exception() -> &'static str {
// The blank line between the error header and the stack trace must contain
// exactly two spaces — the ruby rule `^ $` matches it to transition from
// RubyBeforeRailsTrace to Ruby. Using concat! so rustfmt cannot strip them.
concat!(
"\n",
" ActionController::RoutingError (No route matches [GET] \"/settings\"):\n",
" \n",
" actionpack (5.1.4) lib/action_dispatch/middleware/debug_exceptions.rb:63:in `call'\n",
" actionpack (5.1.4) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'\n",
" railties (5.1.4) lib/rails/rack/logger.rb:36:in `call_app'\n",
" railties (5.1.4) lib/rails/rack/logger.rb:24:in `block in call'\n",
" activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `block in tagged'\n",
" activesupport (5.1.4) lib/active_support/tagged_logging.rb:26:in `tagged'\n",
" activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `tagged'\n",
" railties (5.1.4) lib/rails/rack/logger.rb:24:in `call'\n",
" actionpack (5.1.4) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'\n",
" actionpack (5.1.4) lib/action_dispatch/middleware/request_id.rb:25:in `call'\n",
" rack (2.0.3) lib/rack/method_override.rb:22:in `call'\n",
" rack (2.0.3) lib/rack/runtime.rb:22:in `call'\n",
" activesupport (5.1.4) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call'\n",
" actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'\n",
" rack (2.0.3) lib/rack/sendfile.rb:111:in `call'\n",
" railties (5.1.4) lib/rails/engine.rb:522:in `call'\n",
" puma (3.10.0) lib/puma/configuration.rb:225:in `call'\n",
" puma (3.10.0) lib/puma/server.rb:605:in `handle_request'\n",
" puma (3.10.0) lib/puma/server.rb:437:in `process_client'\n",
" puma (3.10.0) lib/puma/server.rb:301:in `block in run'\n",
" puma (3.10.0) lib/puma/thread_pool.rb:120:in `block in spawn_thread'\n",
" ",
)
#[rustfmt::skip]
const fn rails_exception() -> &'static str {
r#"
ActionController::RoutingError (No route matches [GET] "/settings"):

actionpack (5.1.4) lib/action_dispatch/middleware/debug_exceptions.rb:63:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
railties (5.1.4) lib/rails/rack/logger.rb:36:in `call_app'
railties (5.1.4) lib/rails/rack/logger.rb:24:in `block in call'
activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `block in tagged'
activesupport (5.1.4) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `tagged'
railties (5.1.4) lib/rails/rack/logger.rb:24:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/request_id.rb:25:in `call'
rack (2.0.3) lib/rack/method_override.rb:22:in `call'
rack (2.0.3) lib/rack/runtime.rb:22:in `call'
activesupport (5.1.4) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call'
actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'
rack (2.0.3) lib/rack/sendfile.rb:111:in `call'
railties (5.1.4) lib/rails/engine.rb:522:in `call'
puma (3.10.0) lib/puma/configuration.rb:225:in `call'
puma (3.10.0) lib/puma/server.rb:605:in `handle_request'
puma (3.10.0) lib/puma/server.rb:437:in `process_client'
puma (3.10.0) lib/puma/server.rb:301:in `block in run'
puma (3.10.0) lib/puma/thread_pool.rb:120:in `block in spawn_thread'
"#
}

#[test]
Expand All @@ -614,6 +623,61 @@ Exception: ('spam', 'eggs')
"#
}

#[test]
fn test_exception_keyword_resets_on_non_matching_line() {
// LOG-9963: After an exception keyword triggers JavaAfterException,
// a line that doesn't match any continuation pattern must reset
// the state machine to StartState (NoTrace or EndTrace), NOT
// continue accumulating.
let mut detector = ExceptionDetector {
state_machine: Arc::new(get_state_machines(default_programming_languages())),
current_state: ExceptionState::StartState,
};

// Exception keyword triggers the state machine.
let status = detector.update(&"SecretConfigException: config error".to_string());
assert_eq!(InsideTrace, status);
assert_ne!(ExceptionState::StartState, detector.current_state);

// Non-matching line: transition from JavaAfterException fails, retry
// from StartState also fails → NoTrace. The TraceAccumulator treats
// this as "flush accumulated + emit current", so no merging occurs.
let status = detector.update(&"Normal operation completed".to_string());
assert_eq!(NoTrace, status);
assert_eq!(ExceptionState::StartState, detector.current_state);

// Subsequent non-exception line: already in StartState, no match.
let status = detector.update(&"Another normal log".to_string());
assert_eq!(NoTrace, status);
assert_eq!(ExceptionState::StartState, detector.current_state);
}

#[test]
fn test_consecutive_exception_keywords_each_start_new_trace() {
// LOG-9963: Independent lines each containing exception keywords
// must each be treated as separate trace starts, not merged.
let mut detector = ExceptionDetector {
state_machine: Arc::new(get_state_machines(default_programming_languages())),
current_state: ExceptionState::StartState,
};

let status = detector.update(&"ConnectTimeoutException: host unreachable".to_string());
assert_eq!(InsideTrace, status);

// Second exception keyword: should end previous trace and start new.
let status = detector.update(&"RestException: upstream returned 503".to_string());
assert_eq!(StartTrace, status);

// Third exception keyword: same behavior.
let status = detector.update(&"IllegalArgumentException: null".to_string());
assert_eq!(StartTrace, status);

// Non-exception line: no match from JavaAfterException or StartState.
let status = detector.update(&"Request processed".to_string());
assert_eq!(NoTrace, status);
assert_eq!(ExceptionState::StartState, detector.current_state);
}

fn split(line: &str) -> Vec<&str> {
line.trim().split("\n").collect::<Vec<&str>>()
}
Expand Down
Loading