diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6a967d269..5fa2c1b920 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -401,6 +401,38 @@ jobs: if: failure() run: ./scripts/ci-diagnostics.sh + # This will be replaced once #6945 is merged. + swiftybeaver-integration-unit-tests: + name: SentrySwiftyBeaver Unit Tests + if: needs.files-changed.outputs.run_unit_tests_for_prs == 'true' + needs: files-changed + runs-on: macos-15 + steps: + - uses: actions/checkout@v6 + + - name: Select Xcode + run: ./scripts/ci-select-xcode.sh 16.4 + + - name: Setup local sentry-cocoa dependency + working-directory: 3rd-party-integrations/SentrySwiftyBeaver + run: swift package edit sentry-cocoa --path ../.. + + - name: Run SwiftyBeaver tests + working-directory: 3rd-party-integrations/SentrySwiftyBeaver + run: swift test + + - name: Archiving Raw Logs + uses: actions/upload-artifact@v5 + if: ${{ failure() || cancelled() }} + with: + name: raw-output-swiftybeaver-integration + path: | + 3rd-party-integrations/SentrySwiftyBeaver/.build/**/*.log + + - name: Run CI Diagnostics + if: failure() + run: ./scripts/ci-diagnostics.sh + # This check validates that either all unit tests passed or were skipped, which allows us # to make unit tests a required check with only running the unit tests when required. # So, we don't have to run unit tests, for example, for Changelog or ReadMe changes. diff --git a/3rd-party-integrations/SentrySwiftyBeaver/.gitignore b/3rd-party-integrations/SentrySwiftyBeaver/.gitignore new file mode 100644 index 0000000000..cffaa2c670 --- /dev/null +++ b/3rd-party-integrations/SentrySwiftyBeaver/.gitignore @@ -0,0 +1,100 @@ +# --- macOS --- + +# General +.DS_Store +__MACOSX/ +.AppleDouble +.LSOverride +Icon[] + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# --- Swift --- + +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# --- Xcode --- + +## User settings +xcuserdata/ + +# Archive +*.xcarchive diff --git a/3rd-party-integrations/SentrySwiftyBeaver/Package.swift b/3rd-party-integrations/SentrySwiftyBeaver/Package.swift new file mode 100644 index 0000000000..8b387945fb --- /dev/null +++ b/3rd-party-integrations/SentrySwiftyBeaver/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:6.0 +import PackageDescription + +let package = Package( + name: "SentrySwiftyBeaver", + platforms: [.iOS(.v15), .macOS(.v10_14), .tvOS(.v15), .watchOS(.v8), .visionOS(.v1)], + products: [ + .library( + name: "SentrySwiftyBeaver", + targets: ["SentrySwiftyBeaver"] + ) + ], + dependencies: [ + .package(url: "https://github.com/getsentry/sentry-cocoa", from: "9.0.0"), + .package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver.git", from: "2.0.0") + ], + targets: [ + .target( + name: "SentrySwiftyBeaver", + dependencies: [ + .product(name: "Sentry", package: "sentry-cocoa"), + .product(name: "SwiftyBeaver", package: "SwiftyBeaver") + ] + ), + .testTarget( + name: "SentrySwiftyBeaverTests", + dependencies: [ + "SentrySwiftyBeaver", + .product(name: "Sentry", package: "sentry-cocoa"), + .product(name: "SwiftyBeaver", package: "SwiftyBeaver") + ] + ) + ] +) diff --git a/3rd-party-integrations/SentrySwiftyBeaver/README.md b/3rd-party-integrations/SentrySwiftyBeaver/README.md new file mode 100644 index 0000000000..c864e66072 --- /dev/null +++ b/3rd-party-integrations/SentrySwiftyBeaver/README.md @@ -0,0 +1,116 @@ +# Sentry SwiftyBeaver Integration + +A [SwiftyBeaver](https://github.com/SwiftyBeaver/SwiftyBeaver) destination that forwards log entries to Sentry's structured logging system, automatically capturing application logs with full context including metadata, source location, and log levels. + +> [!NOTE] +> This repo is a mirror of [github.com/getsentry/sentry-cocoa](https://github.com/getsentry/sentry-cocoa). The source code lives in `3rd-party-integrations/SentrySwiftyBeaver/`. This allows users to import only what they need via SPM while keeping all integration code in the main repository. + +## Installation + +### Swift Package Manager + +Add the following dependencies to your `Package.swift` or Xcode package dependencies: + +```swift +dependencies: [ + .package(url: "https://github.com/getsentry/sentry-cocoa-swiftybeaver", from: "9.0.0") +] +``` + +## Quick Start + +```swift +import Sentry +import SwiftyBeaver + +SentrySDK.start { options in + options.dsn = "YOUR_DSN" + options.logsEnabled = true +} + +let log = SwiftyBeaver.self +let sentryDestination = SentryDestination() +log.addDestination(sentryDestination) + +log.info("User logged in", context: ["userId": "12345", "sessionId": "abc"]) +``` + +## Configuration + +### Log Level Threshold + +SwiftyBeaver automatically filters log messages based on each destination's `minLevel` property. Set `minLevel` to control which messages are sent to Sentry. + +```swift +let sentryDestination = SentryDestination() +sentryDestination.minLevel = .error +log.addDestination(sentryDestination) +``` + +## Log Level Mapping + +SwiftyBeaver levels are automatically mapped to Sentry log levels: + +| SwiftyBeaver Level | Sentry Log Level | +| ------------------ | ---------------- | +| `.verbose` | `.trace` | +| `.debug` | `.debug` | +| `.info` | `.info` | +| `.warning` | `.warn` | +| `.error` | `.error` | +| `.critical` | `.fatal` | +| `.fault` | `.fatal` | + +## Context Handling + +The destination supports SwiftyBeaver's `context` parameter for additional metadata: + +### Dictionary Context + +When `context` is provided as a `[String: Any]` dictionary, each key-value pair is added as an individual Sentry log attribute with the prefix `swiftybeaver.context.{key}`. + +```swift +log.info("User action", context: [ + "userId": "12345", + "action": "purchase", + "isActive": true, + "errorCode": 500, + "amount": 99.99 +]) +``` + +Supported types in dictionary context: + +- Strings +- Numbers (Int, Double, Float) +- Booleans +- Arrays (converted to string representation) + +### Non-Dictionary Context + +For non-dictionary contexts, the entire context is converted to a string attribute `swiftybeaver.context`. + +```swift +log.info("Test message", context: "simple string context") +``` + +## Automatic Attributes + +The destination automatically includes the following attributes with every log entry: + +- `sentry.origin`: `"auto.logging.swiftybeaver"` +- `swiftybeaver.level`: The original SwiftyBeaver level (as raw value) +- `swiftybeaver.thread`: The thread identifier +- `swiftybeaver.file`: The source file name +- `swiftybeaver.function`: The function name +- `swiftybeaver.line`: The line number + +## Documentation + +- [Sentry Cocoa SDK Documentation](https://docs.sentry.io/platforms/apple/) +- [Sentry Logs Documentation](https://docs.sentry.io/platforms/apple/logs/) +- [SwiftyBeaver Repo](https://github.com/SwiftyBeaver/SwiftyBeaver) + +## License + +This integration follows the same license as the Sentry Cocoa SDK. See the [LICENSE](https://github.com/getsentry/sentry-cocoa/blob/main/LICENSE.md) file for details. diff --git a/3rd-party-integrations/SentrySwiftyBeaver/Sources/SentryDestination.swift b/3rd-party-integrations/SentrySwiftyBeaver/Sources/SentryDestination.swift new file mode 100644 index 0000000000..2c124b264b --- /dev/null +++ b/3rd-party-integrations/SentrySwiftyBeaver/Sources/SentryDestination.swift @@ -0,0 +1,109 @@ +import Sentry +import SwiftyBeaver + +/// A SwiftyBeaver destination that forwards log entries to Sentry's structured logging system. +/// +/// `SentryDestination` extends SwiftyBeaver's `BaseDestination`, allowing you to integrate +/// Sentry's structured logging capabilities with SwiftyBeaver. This enables you to capture +/// application logs from SwiftyBeaver and send them to Sentry for analysis and monitoring. +/// +/// ## Level Mapping +/// SwiftyBeaver levels are mapped to Sentry log levels: +/// - `.verbose` → `.trace` +/// - `.debug` → `.debug` +/// - `.info` → `.info` +/// - `.warning` → `.warn` +/// - `.error` → `.error` +/// - `.critical` → `.fatal` +/// - `.fault` → `.fatal` +/// +/// ## Context Handling +/// When `context` is provided as a `[String: Any]` dictionary, each key-value pair is added +/// as an individual Sentry log attribute with the prefix `swiftybeaver.context.{key}`. For non-dictionary +/// contexts, the entire context is converted to a string attribute `swiftybeaver.context`. +/// +/// ## Usage +/// ```swift +/// import Sentry +/// import SwiftyBeaver +/// +/// SentrySDK.start { options in +/// options.dsn = "YOUR_DSN" +/// options.logsEnabled = true +/// } +/// +/// let log = SwiftyBeaver.self +/// let sentryDestination = SentryDestination() +/// log.addDestination(sentryDestination) +/// +/// log.info("User logged in", context: ["userId": "12345", "sessionId": "abc"]) +/// ``` +public class SentryDestination: BaseDestination { + + /// Sends a log message to Sentry's structured logging system. + /// + /// - Parameters: + /// - level: The SwiftyBeaver log level + /// - msg: The log message + /// - thread: The thread identifier + /// - file: The source file name + /// - function: The function name + /// - line: The line number + /// - context: Additional context information + /// - Returns: Always returns `nil` as per SwiftyBeaver convention + public override func send( + _ level: SwiftyBeaver.Level, + msg: String, + thread: String, + file: String, + function: String, + line: Int, + context: Any? = nil + ) -> String? { + var attributes: [String: Any] = [:] + attributes["sentry.origin"] = "auto.logging.swiftybeaver" + attributes["swiftybeaver.level"] = "\(level.rawValue)" + attributes["swiftybeaver.thread"] = thread + attributes["swiftybeaver.file"] = file + attributes["swiftybeaver.function"] = function + attributes["swiftybeaver.line"] = "\(line)" + + if let context = context { + addContextToAttributes(&attributes, context: context) + } + + logToSentry(level: level, message: msg, attributes: attributes) + return nil + } + + private func addContextToAttributes(_ attributes: inout [String: Any], context: Any) { + if let contextDict = context as? [String: Any] { + for (key, value) in contextDict { + attributes["swiftybeaver.context.\(key)"] = value + } + } else { + attributes["swiftybeaver.context"] = "\(context)" + } + } + + private func logToSentry(level: SwiftyBeaver.Level, message: String, attributes: [String: Any]) { + switch level { + case .verbose: + SentrySDK.logger.trace(message, attributes: attributes) + case .debug: + SentrySDK.logger.debug(message, attributes: attributes) + case .info: + SentrySDK.logger.info(message, attributes: attributes) + case .warning: + SentrySDK.logger.warn(message, attributes: attributes) + case .error: + SentrySDK.logger.error(message, attributes: attributes) + case .critical: + SentrySDK.logger.fatal(message, attributes: attributes) + case .fault: + SentrySDK.logger.fatal(message, attributes: attributes) + @unknown default: + SentrySDK.logger.error(message, attributes: attributes) + } + } +} diff --git a/3rd-party-integrations/SentrySwiftyBeaver/Tests/SentryDestinationTests.swift b/3rd-party-integrations/SentrySwiftyBeaver/Tests/SentryDestinationTests.swift new file mode 100644 index 0000000000..ea1768a228 --- /dev/null +++ b/3rd-party-integrations/SentrySwiftyBeaver/Tests/SentryDestinationTests.swift @@ -0,0 +1,218 @@ +import Sentry +@testable import SentrySwiftyBeaver +import SwiftyBeaver +import XCTest + +final class SentryDestinationTests: XCTestCase { + + private var capturedLogs: [SentryLog] = [] + + override func setUp() { + super.setUp() + capturedLogs = [] + SentrySDK.start { options in + options.dsn = "https://test@test.ingest.sentry.io/123456" + options.enableLogs = true + options.beforeSendLog = { [weak self] log in + self?.capturedLogs.append(log) + return nil + } + } + } + + override func tearDown() { + super.tearDown() + SentrySDK.close() + capturedLogs = [] + } + + // MARK: - Basic Logging Tests + + func testVerboseMapsToTrace() throws { + let sut = SentryDestination() + let result = sut.send(.verbose, msg: "Verbose message", thread: "main", file: "Test.swift", function: "testFunction", line: 42) + + XCTAssertEqual(capturedLogs.count, 1, "Expected exactly one log to be captured") + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .trace) + XCTAssertEqual(log.body, "Verbose message") + XCTAssertEqual(log.attributes["sentry.origin"]?.value as? String, "auto.logging.swiftybeaver") + XCTAssertEqual(log.attributes["swiftybeaver.level"]?.value as? String, "0") + XCTAssertEqual(log.attributes["swiftybeaver.thread"]?.value as? String, "main") + XCTAssertEqual(log.attributes["swiftybeaver.file"]?.value as? String, "Test.swift") + XCTAssertEqual(log.attributes["swiftybeaver.function"]?.value as? String, "testFunction") + XCTAssertEqual(log.attributes["swiftybeaver.line"]?.value as? String, "42") + + XCTAssertNil(result) + } + + func testDebugMapsToDebug() throws { + let sut = SentryDestination() + let result = sut.send(.debug, msg: "Debug message", thread: "main", file: "Test.swift", function: "testFunction", line: 50) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .debug) + XCTAssertEqual(log.body, "Debug message") + XCTAssertEqual(log.attributes["sentry.origin"]?.value as? String, "auto.logging.swiftybeaver") + XCTAssertEqual(log.attributes["swiftybeaver.level"]?.value as? String, "1") + XCTAssertEqual(log.attributes["swiftybeaver.thread"]?.value as? String, "main") + XCTAssertEqual(log.attributes["swiftybeaver.file"]?.value as? String, "Test.swift") + XCTAssertEqual(log.attributes["swiftybeaver.function"]?.value as? String, "testFunction") + XCTAssertEqual(log.attributes["swiftybeaver.line"]?.value as? String, "50") + + XCTAssertNil(result) + } + + func testInfoMapsToInfo() throws { + let sut = SentryDestination() + let result = sut.send(.info, msg: "Info message", thread: "main", file: "Test.swift", function: "testFunction", line: 100) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .info) + XCTAssertEqual(log.body, "Info message") + XCTAssertEqual(log.attributes["sentry.origin"]?.value as? String, "auto.logging.swiftybeaver") + XCTAssertEqual(log.attributes["swiftybeaver.level"]?.value as? String, "2") + XCTAssertEqual(log.attributes["swiftybeaver.thread"]?.value as? String, "main") + XCTAssertEqual(log.attributes["swiftybeaver.file"]?.value as? String, "Test.swift") + XCTAssertEqual(log.attributes["swiftybeaver.function"]?.value as? String, "testFunction") + XCTAssertEqual(log.attributes["swiftybeaver.line"]?.value as? String, "100") + + XCTAssertNil(result) + } + + func testWarningMapsToWarn() throws { + let sut = SentryDestination() + let result = sut.send(.warning, msg: "Warning message", thread: "main", file: "Test.swift", function: "testFunction", line: 75) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .warn) + XCTAssertEqual(log.body, "Warning message") + XCTAssertEqual(log.attributes["sentry.origin"]?.value as? String, "auto.logging.swiftybeaver") + XCTAssertEqual(log.attributes["swiftybeaver.level"]?.value as? String, "3") + XCTAssertEqual(log.attributes["swiftybeaver.thread"]?.value as? String, "main") + XCTAssertEqual(log.attributes["swiftybeaver.file"]?.value as? String, "Test.swift") + XCTAssertEqual(log.attributes["swiftybeaver.function"]?.value as? String, "testFunction") + XCTAssertEqual(log.attributes["swiftybeaver.line"]?.value as? String, "75") + + XCTAssertNil(result) + } + + func testErrorMapsToError() throws { + let sut = SentryDestination() + let result = sut.send(.error, msg: "Error message", thread: "main", file: "Test.swift", function: "testFunction", line: 200) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .error) + XCTAssertEqual(log.body, "Error message") + XCTAssertEqual(log.attributes["sentry.origin"]?.value as? String, "auto.logging.swiftybeaver") + XCTAssertEqual(log.attributes["swiftybeaver.level"]?.value as? String, "4") + XCTAssertEqual(log.attributes["swiftybeaver.thread"]?.value as? String, "main") + XCTAssertEqual(log.attributes["swiftybeaver.file"]?.value as? String, "Test.swift") + XCTAssertEqual(log.attributes["swiftybeaver.function"]?.value as? String, "testFunction") + XCTAssertEqual(log.attributes["swiftybeaver.line"]?.value as? String, "200") + + XCTAssertNil(result) + } + + func testCriticalMapsToFatal() throws { + let sut = SentryDestination() + let result = sut.send(.critical, msg: "Critical message", thread: "main", file: "Test.swift", function: "testFunction", line: 250) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .fatal) + XCTAssertEqual(log.body, "Critical message") + XCTAssertEqual(log.attributes["sentry.origin"]?.value as? String, "auto.logging.swiftybeaver") + XCTAssertEqual(log.attributes["swiftybeaver.level"]?.value as? String, "5") + XCTAssertEqual(log.attributes["swiftybeaver.thread"]?.value as? String, "main") + XCTAssertEqual(log.attributes["swiftybeaver.file"]?.value as? String, "Test.swift") + XCTAssertEqual(log.attributes["swiftybeaver.function"]?.value as? String, "testFunction") + XCTAssertEqual(log.attributes["swiftybeaver.line"]?.value as? String, "250") + + XCTAssertNil(result) + } + + func testFaultMapsToFatal() throws { + let sut = SentryDestination() + let result = sut.send(.fault, msg: "Fault message", thread: "main", file: "Test.swift", function: "testFunction", line: 300) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + XCTAssertEqual(log.level, .fatal) + XCTAssertEqual(log.body, "Fault message") + XCTAssertEqual(log.attributes["sentry.origin"]?.value as? String, "auto.logging.swiftybeaver") + XCTAssertEqual(log.attributes["swiftybeaver.level"]?.value as? String, "6") + XCTAssertEqual(log.attributes["swiftybeaver.thread"]?.value as? String, "main") + XCTAssertEqual(log.attributes["swiftybeaver.file"]?.value as? String, "Test.swift") + XCTAssertEqual(log.attributes["swiftybeaver.function"]?.value as? String, "testFunction") + XCTAssertEqual(log.attributes["swiftybeaver.line"]?.value as? String, "300") + + XCTAssertNil(result) + } + + func testContextDictionary_WithAllSupportedTypes() throws { + let sut = SentryDestination() + let context: [String: Any] = [ + "userId": "12345", // String + "isActive": true, // Bool + "errorCode": 500, // Int + "amount": 99.99, // Double + "temperature": Float(36.6), // Float (converted to Double) + "tags": ["production", "api", "v2"] // Array (unsupported, converted to String) + ] + let result = sut.send(.error, msg: "Payment failed", thread: "main", file: "Test.swift", function: "testFunction", line: 150, context: context) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + + // Verify all attribute types are preserved correctly + XCTAssertEqual(log.attributes["swiftybeaver.context.userId"]?.type, "string") + XCTAssertEqual(log.attributes["swiftybeaver.context.userId"]?.value as? String, "12345") + + XCTAssertEqual(log.attributes["swiftybeaver.context.isActive"]?.type, "boolean") + XCTAssertEqual(log.attributes["swiftybeaver.context.isActive"]?.value as? Bool, true) + + XCTAssertEqual(log.attributes["swiftybeaver.context.errorCode"]?.type, "integer") + XCTAssertEqual(log.attributes["swiftybeaver.context.errorCode"]?.value as? Int, 500) + + XCTAssertEqual(log.attributes["swiftybeaver.context.amount"]?.type, "double") + if let amountValue = log.attributes["swiftybeaver.context.amount"]?.value as? Double { + XCTAssertEqual(amountValue, 99.99, accuracy: 0.001) + } else { + XCTFail("Amount value should be a Double") + } + + XCTAssertEqual(log.attributes["swiftybeaver.context.temperature"]?.type, "double") + if let tempValue = log.attributes["swiftybeaver.context.temperature"]?.value as? Double { + XCTAssertEqual(tempValue, 36.6, accuracy: 0.01) + } else { + XCTFail("Temperature value should be a Double") + } + + // Verify unsupported type (array) is converted to string + XCTAssertEqual(log.attributes["swiftybeaver.context.tags"]?.type, "string") + let tagsValue = log.attributes["swiftybeaver.context.tags"]?.value as? String + XCTAssertNotNil(tagsValue) + XCTAssertTrue(tagsValue?.contains("production") ?? false, "Tags string should contain 'production'") + XCTAssertTrue(tagsValue?.contains("api") ?? false, "Tags string should contain 'api'") + XCTAssertTrue(tagsValue?.contains("v2") ?? false, "Tags string should contain 'v2'") + + XCTAssertNil(result) + } + + func testContextNonDictionary_ConvertsToString() throws { + let sut = SentryDestination() + let context = "simple string context" + _ = sut.send(.info, msg: "Test message", thread: "main", file: "Test.swift", function: "testFunction", line: 10, context: context) + + XCTAssertEqual(capturedLogs.count, 1) + let log = try XCTUnwrap(capturedLogs.first) + + XCTAssertNotNil(log.attributes["swiftybeaver.context"]) + XCTAssertEqual(log.attributes["swiftybeaver.context"]?.value as? String, "simple string context") + } +} diff --git a/Utils/VersionBump/main.swift b/Utils/VersionBump/main.swift index fe6ad54076..a835a48939 100644 --- a/Utils/VersionBump/main.swift +++ b/Utils/VersionBump/main.swift @@ -42,7 +42,8 @@ let files = [ "./SentrySwiftUI.podspec", "./Sources/Sentry/SentryMeta.m", "./Tests/HybridSDKTest/HybridPod.podspec", - "./3rd-party-integrations/SentrySwiftLog/Package.swift" + "./3rd-party-integrations/SentrySwiftLog/Package.swift", + "./3rd-party-integrations/SentrySwiftyBeaver/Package.swift" ] // Files that only accept the format x.x.x in order to release an app using the framework.