Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d853f5e
refactor: remove nonisolated(unsafe) modifier from explicitTag and bu…
syxc Jan 8, 2026
627ac2c
feat: add unit tests for AsyncTree, CrashBufferTree, DebugTree, and T…
syxc Jan 8, 2026
9e10b62
feat: add custom SwiftLint rules for Canopy logging framework
syxc Jan 8, 2026
1981294
feat: add initial scheme configuration for Canopy project
syxc Jan 8, 2026
a739d0e
docs: update Chinese documentation filename to follow locale convention
syxc Jan 8, 2026
0ed7016
docs: rename Chinese documentation file to standard locale format
syxc Jan 8, 2026
1b5c10b
docs: add best practices, performance analysis and testing guide
syxc Jan 8, 2026
bbba69d
docs(README.zh-CN): update documentation with comprehensive best prac…
syxc Jan 8, 2026
0ec06ce
fix: update logging message format specifiers to use correct types
syxc Jan 8, 2026
971dbf5
refactor: enhance thread safety and performance across logging trees
syxc Jan 8, 2026
4a290c2
refactor: improve variable declarations and initialization consistenc…
syxc Jan 8, 2026
2d383f4
refactor: remove unused custom rules and clean up SwiftLint configura…
syxc Jan 8, 2026
feedb2d
chore: add .gitattributes file to manage line endings and binary files
syxc Jan 8, 2026
ea7fa65
chore: add .editorconfig to standardize code formatting and style gui…
syxc Jan 8, 2026
e2778a8
chore: add CI workflow for SwiftLint, iOS build, and testing
syxc Jan 8, 2026
52fb52d
chore: update CI workflow to ignore additional file types in push and…
syxc Jan 8, 2026
9dee487
chore: update CI workflow to use macOS 15 and add iOS 18.0 to test ma…
syxc Jan 8, 2026
029dc57
chore: update CI workflow to remove iOS 18.0 from test matrix and adj…
syxc Jan 8, 2026
6f0d5e8
chore: simplify CI workflow by removing iOS version matrix and unnece…
syxc Jan 8, 2026
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
24 changes: 24 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
max_line_length = 120
insert_final_newline = true
trim_trailing_whitespace = true

[*.{js,ts,css,html}]
indent_size = 2

[*.{md,mdx,diff}]
indent_size = 2
trim_trailing_whitespace = false

[Makefile]
tab_width = 4
indent_style = tab

[COMMIT_EDITMSG]
max_line_length = 0
10 changes: 10 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf

*.jar binary
*.png binary
*.jpg binary
*.jpeg binary
*.webp binary
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Canopy CI

on:
push:
branches: [main, master]
paths-ignore:
- '**.md'
- '**.txt'
- 'docs/**'
- 'Examples/**'
- 'README*'
- 'CONTRIBUTING*'
- 'TESTING*'
pull_request:
branches: [main, master]
paths-ignore:
- '**.md'
- '**.txt'
- 'docs/**'
- 'Examples/**'
- 'README*'
- 'CONTRIBUTING*'
- 'TESTING*'

jobs:
lint:
name: SwiftLint
runs-on: macos-15
steps:
- uses: actions/checkout@v4

- name: Setup SwiftLint
run: brew install swiftlint

- name: Run SwiftLint
run: swiftlint

build-and-test:
name: Build & Test
runs-on: macos-15
steps:
- uses: actions/checkout@v4

- name: Build for iOS Simulator
run: |
xcodebuild -project Canopy.xcodeproj \
-scheme Canopy \
-destination "generic/platform=iOS Simulator" \
-configuration Debug \
build \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO

- name: Run SPM Tests
run: swift test

67 changes: 67 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
disabled_rules:
- trailing_whitespace
- todo
- multiple_closures_with_trailing_closure
- optional_data_string_conversion
- function_parameter_count

opt_in_rules:
- empty_count
- empty_string
- force_unwrapping
- explicit_init
- first_where
- overridden_super_call
- redundant_nil_coalescing
- vertical_whitespace_closing_braces
- weak_delegate

line_length:
warning: 120
error: 150
ignores_function_declarations: true
ignores_comments: true

file_length:
warning: 500
error: 1000

function_body_length:
warning: 60
error: 100

type_body_length:
warning: 300
error: 500

identifier_name:
min_length:
warning: 3
error: 2
max_length:
warning: 40
error: 50

type_name:
min_length:
warning: 3
error: 2
max_length:
warning: 40
error: 50

large_tuple:
warning: 3
error: 4

excluded:
- Carthage
- Pods
- Canopy.xcodeproj
- .build

included:
- Canopy
- Sources
- Examples
- Tests
78 changes: 78 additions & 0 deletions Canopy.xcodeproj/xcshareddata/xcschemes/Canopy.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4C77792D2F0EE54C001D1F0B"
BuildableName = "Canopy.app"
BlueprintName = "Canopy"
ReferencedContainer = "container:Canopy.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4C77792D2F0EE54C001D1F0B"
BuildableName = "Canopy.app"
BlueprintName = "Canopy"
ReferencedContainer = "container:Canopy.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4C77792D2F0EE54C001D1F0B"
BuildableName = "Canopy.app"
BlueprintName = "Canopy"
ReferencedContainer = "container:Canopy.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
5 changes: 3 additions & 2 deletions Canopy/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
Canopy.plant(DebugTree())
#endif

crashBufferTree = CrashBufferTree(maxSize: 50)
Canopy.plant(crashBufferTree!)
let crashTree = CrashBufferTree(maxSize: 50)
crashBufferTree = crashTree
Canopy.plant(crashTree)

Canopy.v("Canopy initialized with DebugTree and CrashBufferTree")
}
Expand Down
6 changes: 1 addition & 5 deletions Canopy/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
guard (scene as? UIWindowScene) != nil else { return }
}

func sceneDidDisconnect(_ scene: UIScene) {
Expand Down Expand Up @@ -46,7 +45,4 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}


}

14 changes: 8 additions & 6 deletions Canopy/Sources/AsyncTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

import Foundation

public final class AsyncTree: Tree {
/// A Tree wrapper that executes logs asynchronously on a background queue.
/// This prevents logging operations from blocking the calling thread.
public final class AsyncTree: Tree, @unchecked Sendable {
private let wrapped: Tree
private let queue: DispatchQueue

Expand All @@ -32,19 +34,19 @@ public final class AsyncTree: Tree {
line: UInt
) {
let currentContext = CanopyContext.current
let capturedTag = self.explicitTag
self.explicitTag = nil // Clear immediately to prevent affecting subsequent logs
let capturedTag = explicitTag
explicitTag = nil
let capturedMessage = formatMessage(message(), arguments)

queue.async {
queue.async { [wrapped] in
let previous = CanopyContext.current
CanopyContext.current = currentContext

self.wrapped.log(
wrapped.log(
priority: priority,
tag: capturedTag,
message: capturedMessage,
arguments: arguments,
arguments: [],
error: error,
file: file,
function: function,
Expand Down
Loading