Skip to content
Merged
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
48 changes: 48 additions & 0 deletions common/constants/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package constants

const (
Underscore = "_"
UserHome = "user.home"
)

type SymbolFlag int64

const (
PUBLIC SymbolFlag = 1 << iota
NATIVE
FINAL
ATTACHED
READONLY
REQUIRED
PRIVATE
OPTIONAL
REMOTE
CLIENT
RESOURCE
SERVICE
TRANSACTIONAL
CLASS
ISOLATED
ENUM
ANY_FUNCTION
)

func (sf SymbolFlag) IsOn(flag SymbolFlag) bool {
return (sf & flag) == flag
}
93 changes: 92 additions & 1 deletion common/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@

package errors

import "fmt"
import (
"bytes"
"fmt"
"log"
"os"
"runtime"
"time"
)

type IndexOutOfBoundsError struct {
index int
Expand Down Expand Up @@ -59,3 +66,87 @@ func NewIllegalArgumentError(argument any) *IllegalArgumentError {
argument: argument,
}
}

type errorWithStackTrace struct {
err error
stack []uintptr
}

func (e *errorWithStackTrace) Error() string {
if e.stack != nil {
return fmt.Sprintf("%s%s", e.err.Error(), e.stackTrace())
}
return e.err.Error()
}

func DecorateWithStackTrace(err error) error {
if err == nil || os.Getenv("BAL_BACKTRACE") != "true" {
return err
}

stack := make([]uintptr, 32)
length := runtime.Callers(2, stack[:])
return &errorWithStackTrace{
err: err,
stack: stack[:length],
}
}

func (e *errorWithStackTrace) stackTrace() []byte {
if e == nil || len(e.stack) == 0 {
return nil
}

var buf bytes.Buffer
frames := runtime.CallersFrames(e.stack)
for {
frame, more := frames.Next()
fmt.Fprintf(&buf, "\n\tat %s(%s:%d)", frame.Function, frame.File, frame.Line)
if !more {
break
}
}

return buf.Bytes()
}

const (
logSource = "ballerina"
logLevel = "SEVERE"
internalErrorMessage = `ballerina: Oh no, something really went wrong. Bad. Sad.

We appreciate it if you can report the code that broke Ballerina in
https://github.com/ballerina-platform/ballerina-lang/issues with the
log you get below and your sample code.

We thank you for helping make us better.
`
)

// LogBadSad logs unhandled errors with an internal error message.
// These are unexpected errors in the runtime that should be reported.
func LogBadSad(err error) {
fmt.Fprint(os.Stderr, internalErrorMessage)
PrintCrashLog(err)
}

// PrintCrashLog logs error messages to stderr in a structured format.
// Format: [timestamp] LEVEL {source} - error message
func PrintCrashLog(err error) {
if err == nil {
return
}

now := time.Now()
timestamp := now.Format("2006-01-02 15:04:05") + fmt.Sprintf(",%03d", now.Nanosecond()/1e6)

msg := fmt.Sprintf("[%s] %-5s {%s} - %s",
timestamp,
logLevel,
logSource,
err.Error(),
)

logger := log.New(os.Stderr, "", 0)
logger.Println(msg)
}
104 changes: 104 additions & 0 deletions common/errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package errors

import (
"errors"
"os"
"strings"
"testing"
)

func TestDecorateWithStackTrace_WithBacktraceDisabled(t *testing.T) {
// Ensure BAL_BACKTRACE is not set
os.Unsetenv("BAL_BACKTRACE")

err := errors.New("test error")
decorated := DecorateWithStackTrace(err)

// When backtrace is disabled, should return the original error
if decorated.Error() != "test error" {
t.Errorf("Expected 'test error', got '%s'", decorated.Error())
}

// Should be the same error reference when backtrace is disabled
if decorated != err {
t.Error("Expected original error when backtrace is disabled")
}
}

func TestDecorateWithStackTrace_WithBacktraceEnabled(t *testing.T) {
// Enable backtrace
os.Setenv("BAL_BACKTRACE", "true")
defer os.Unsetenv("BAL_BACKTRACE")

err := errors.New("test error with stack")
decorated := DecorateWithStackTrace(err)

// Should not be the original error when backtrace is enabled
if decorated == err {
t.Fatal("Expected decorated error when backtrace is enabled")
}

// Error message should contain original error
if !strings.Contains(decorated.Error(), "test error with stack") {
t.Errorf("Error message should contain original error text")
}

// Error message should contain stack trace
errorMsg := decorated.Error()
if !strings.Contains(errorMsg, "\n\tat ") {
t.Error("Error message should contain formatted stack trace")
}
}

func TestDecorateWithStackTrace_WithNilError(t *testing.T) {
os.Setenv("BAL_BACKTRACE", "true")
defer os.Unsetenv("BAL_BACKTRACE")

decorated := DecorateWithStackTrace(nil)

if decorated != nil {
t.Error("Expected nil when decorating nil error")
}
}

func TestErrorWithStackTrace_StackTrace(t *testing.T) {
os.Setenv("BAL_BACKTRACE", "true")
defer os.Unsetenv("BAL_BACKTRACE")

err := errors.New("test error")
decorated := DecorateWithStackTrace(err)

// Test through the Error() method which includes the stack trace
errorMsg := decorated.Error()

// Should contain original error message
if !strings.Contains(errorMsg, "test error") {
t.Error("Expected error message to contain original error text")
}

// Should contain function name
if !strings.Contains(errorMsg, "\n\tat ") {
t.Error("Stack trace should contain function frames")
}

// Should contain file path
if !strings.Contains(errorMsg, ".go:") {
t.Error("Stack trace should contain file paths and line numbers")
}
}
Loading