Skip to main content

google_sheets_logger 1.0.2 copy "google_sheets_logger: ^1.0.2" to clipboard
google_sheets_logger: ^1.0.2 copied to clipboard

A lightweight, serverless error tracking system for Flutter Web that routes crashes and API failures directly into a Google Sheet using Google Apps Script.

Google Sheets Logger #

A lightweight, serverless error tracking system for Flutter Web that routes crashes and API failures directly into a Google Sheet using Google Apps Script. No backend server required.

Features #

  • 🚀 Serverless - No backend server needed, uses Google Apps Script
  • 🌐 Flutter Web Optimized - Uses text/plain content-type to bypass CORS preflight
  • 🌍 Auto IP Detection - Automatically captures client IP via ipify
  • 🛡️ Error Resilient - Silently fails if logging fails (never crashes your app)
  • 🔧 Zero Config Backend - Just deploy a Google Apps Script
  • 📱 Cross-platform - Works on Flutter Web, iOS, Android, and Desktop

Getting Started #

1. Add Dependency #

dependencies:
  google_sheets_logger: ^1.0.0

2. Create a Google Apps Script #

  1. Go to script.google.com
  2. Create a new project
  3. Paste this code:
function doPost(e) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const data = JSON.parse(e.postData.contents);
  
  const row = [
    new Date(),                    // Timestamp
    data.projectName,              // Project name
    data.environment,              // Environment (dev/staging/prod)
    data.ipAddress,                // Client IP
    data.errorType,                // Error type (e.g., "API_ERROR", "CRASH")
    data.endpoint || '',           // API endpoint (if applicable)
    data.errorCode || '',          // Error code (if applicable)
    data.errorMessage,             // Error message
    data.stackTrace || ''          // Stack trace
  ];
  
  sheet.appendRow(row);
  
  return ContentService.createTextOutput(JSON.stringify({success: true}))
    .setMimeType(ContentService.MimeType.JSON);
}
  1. Deploy → New Deployment → Web App
    • Execute as: Me
    • Who has access: Anyone
    • Copy the Web App URL (ends with /exec)

3. Initialize in main.dart #

import 'package:flutter/material.dart';
import 'package:google_sheets_logger/google_sheets_logger.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize BEFORE runApp()
  await GoogleSheetsLogger.initialize(
    scriptUrl: 'https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec',
    projectName: 'My Flutter App',
    environment: 'production', // or 'development', 'staging'
  );
  
  runApp(const MyApp());
}

4. Log Errors Manually #

try {
  final response = await http.get(Uri.parse('https://api.example.com/data'));
  if (response.statusCode != 200) {
    throw Exception('API returned ${response.statusCode}');
  }
} catch (e, stackTrace) {
  await GoogleSheetsLogger.logError(
    errorMessage: e.toString(),
    errorType: 'API_ERROR',
    endpoint: 'https://api.example.com/data',
    errorCode: 'HTTP_${response?.statusCode}',
    stackTrace: stackTrace.toString(),
  );
  
  // Show user-friendly error
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Failed to load data. Please try again.')),
  );
}

5. Catch Uncaught Flutter Errors (Optional) #

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  await GoogleSheetsLogger.initialize(
    scriptUrl: 'https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec',
    projectName: 'My Flutter App',
    environment: 'production',
  );
  
  // Catch Flutter framework errors
  FlutterError.onError = (FlutterErrorDetails details) async {
    await GoogleSheetsLogger.logError(
      errorMessage: details.exceptionAsString(),
      errorType: 'FLUTTER_FRAMEWORK_ERROR',
      stackTrace: details.stack?.toString(),
    );
    FlutterError.presentError(details);
  };
  
  // Catch Dart async errors
  PlatformDispatcher.instance.onError = (error, stack) async {
    await GoogleSheetsLogger.logError(
      errorMessage: error.toString(),
      errorType: 'DART_ASYNC_ERROR',
      stackTrace: stack.toString(),
    );
    return true;
  };
  
  runApp(const MyApp());
}

API Reference #

GoogleSheetsLogger.initialize() #

Initializes the logger. Must be called before runApp().

static Future<void> initialize({
  required String scriptUrl,      // Your Google Apps Script Web App URL
  required String projectName,    // Your project/app name
  required String environment,    // 'development' | 'staging' | 'production'
}) async

Parameters:

Parameter Type Required Description
scriptUrl String Yes Google Apps Script Web App URL (ends with /exec)
projectName String Yes Your project/app name for identification
environment String Yes Environment name (e.g., 'production', 'staging', 'development')

GoogleSheetsLogger.logError() #

Manually log an error to Google Sheets.

static Future<void> logError({
  required String errorMessage,   // Human-readable error message
  required String errorType,      // Error category (e.g., 'API_ERROR', 'CRASH')
  String? endpoint,               // API endpoint that failed (optional)
  String? errorCode,              // Error code (e.g., 'HTTP_500', 'TIMEOUT')
  String? stackTrace,             // Stack trace string (optional)
}) async

Parameters:

Parameter Type Required Description
errorMessage String Yes The error message to log
errorType String Yes Category/type of error
endpoint String? No API endpoint URL if applicable
errorCode String? No HTTP status code or custom error code
stackTrace String? No Stack trace as string

Suggested Error Types:

Type Use Case
API_ERROR HTTP API failures
NETWORK_ERROR Network connectivity issues
PARSE_ERROR JSON parsing failures
AUTH_ERROR Authentication/authorization failures
VALIDATION_ERROR Input validation failures
FLUTTER_FRAMEWORK_ERROR Flutter framework errors
DART_ASYNC_ERROR Uncaught Dart async errors
CRASH App crashes
CUSTOM Custom error types

Google Sheets Output Format #

The Google Sheet will receive rows with these columns:

Column Description
A Timestamp (auto-generated)
B Project Name
C Environment
D Client IP Address
E Error Type
F API Endpoint
G Error Code
H Error Message
I Stack Trace

Complete Example: main.dart #

import 'package:flutter/material.dart';
import 'package:google_sheets_logger/google_sheets_logger.dart';
import 'package:flutter/foundation.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize logger BEFORE runApp
  await GoogleSheetsLogger.initialize(
    scriptUrl: const String.fromEnvironment('GOOGLE_SCRIPT_URL'),
    projectName: const String.fromEnvironment('PROJECT_NAME', defaultValue: 'MyApp'),
    environment: const String.fromEnvironment('ENVIRONMENT', defaultValue: 'development'),
  );
  
  // Global error handlers (optional but recommended)
  FlutterError.onError = (details) async {
    await GoogleSheetsLogger.logError(
      errorMessage: details.exceptionAsString(),
      errorType: 'FLUTTER_FRAMEWORK_ERROR',
      stackTrace: details.stack?.toString(),
    );
    FlutterError.presentError(details);
  };
  
  PlatformDispatcher.instance.onError = (error, stack) async {
    await GoogleSheetsLogger.logError(
      errorMessage: error.toString(),
      errorType: 'DART_ASYNC_ERROR',
      stackTrace: stack.toString(),
    );
    return true;
  };
  
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Google Sheets Logger Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _status = 'Ready';
  
  Future<void> _testApiError() async {
    setState(() => _status = 'Testing API error...');
    
    try {
      // This will fail - invalid URL
      await http.get(Uri.parse('https://httpstat.us/500'));
    } catch (e, stack) {
      await GoogleSheetsLogger.logError(
        errorMessage: e.toString(),
        errorType: 'API_ERROR',
        endpoint: 'https://httpstat.us/500',
        errorCode: 'HTTP_500',
        stackTrace: stack.toString(),
      );
      setState(() => _status = 'Error logged! Check your Google Sheet.');
    }
  }
  
  Future<void> _testCrash() async {
    await GoogleSheetsLogger.logError(
      errorMessage: 'Test crash from button press',
      errorType: 'CRASH',
      stackTrace: StackTrace.current.toString(),
    );
    setState(() => _status = 'Test crash logged!');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Google Sheets Logger Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_status, style: const TextStyle(fontSize: 18)),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _testApiError,
              child: const Text('Test API Error'),
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: _testCrash,
              child: const Text('Test Manual Crash Log'),
            ),
          ],
        ),
      ),
    );
  }
}

Use --dart-define for sensitive configuration:

flutter run --dart-define=GOOGLE_SCRIPT_URL=https://script.google.com/macros/s/XXX/exec \
            --dart-define=PROJECT_NAME=MyApp \
            --dart-define=ENVIRONMENT=production
await GoogleSheetsLogger.initialize(
  scriptUrl: const String.fromEnvironment('GOOGLE_SCRIPT_URL'),
  projectName: const String.fromEnvironment('PROJECT_NAME'),
  environment: const String.fromEnvironment('ENVIRONMENT'),
);

Google Apps Script Setup Details #

Sheet Setup #

  1. Create a new Google Sheet
  2. Add headers in row 1:
    Timestamp | Project | Environment | IP Address | Error Type | Endpoint | Error Code | Message | Stack Trace
    
  3. Open Extensions → Apps Script
  4. Paste the doPost function from Quick Start
  5. Deploy as Web App (Anyone, Execute as Me)

CORS Note #

The library uses Content-Type: text/plain to avoid CORS preflight requests on Flutter Web. The Apps Script handles JSON parsing from e.postData.contents.

Security (Optional) #

Add a secret token validation in Apps Script for production:

function doPost(e) {
  const secret = e.parameter.secret;
  if (secret !== PropertiesService.getScriptProperties().getProperty('SECRET')) {
    return ContentService.createTextOutput('Unauthorized').setMimeType(ContentService.MimeType.TEXT);
  }
  // ... rest of code
}

Error Handling Behavior #

Scenario Behavior
Not initialized Logs debug message, returns silently
Network error Catches silently, logs to debug console
CORS error Catches silently (text/plain bypasses preflight)
Apps Script error Catches silently, logs to debug console
App crash Never crashes your app - fails silently

Troubleshooting #

Issue Solution
"Not initialized" error Call initialize() before runApp()
Errors not appearing in Sheet Check Apps Script execution logs (Extensions → Apps Script → Executions)
CORS errors Ensure Apps Script is deployed as "Anyone" access
IP shows "Unknown IP" ipify.org may be blocked; check network
Sheet not updating Check Apps Script execution transcript for errors

License #

MIT License - See LICENSE file for details.

0
likes
35
points
22
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A lightweight, serverless error tracking system for Flutter Web that routes crashes and API failures directly into a Google Sheet using Google Apps Script.

License

MIT (license)

Dependencies

flutter, http, package_info_plus

More

Packages that depend on google_sheets_logger