diff --git a/app/blog/[...slug]/page.tsx b/app/blog/[...slug]/page.tsx new file mode 100644 index 0000000..9376d30 --- /dev/null +++ b/app/blog/[...slug]/page.tsx @@ -0,0 +1,84 @@ +import { blogSource } from '@/lib/source'; +import { notFound } from 'next/navigation'; +import Image from 'next/image'; +import { authors } from '@/lib/authors'; + +// Page params type is defined inline in the component + +export function generateStaticParams() { + return blogSource.getPages().map((page) => ({ + slug: page.slugs, + })); +} + +export default async function BlogPostPage(props: { params: Promise<{ slug: string[] }>}) { + const params = await props.params; + const page = blogSource.getPage(params.slug); + + if (!page) { + notFound(); + } + + return ( +
+
+

{page.data.title}

+ {page.data.description && ( +

+ {page.data.description} +

+ )} + {page.data.date && ( +
+ Published on {new Date(page.data.date).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + })} +
+ )} +
+ {(() => { + const MDXContent = page.data.body as React.ComponentType; + return ; + })()} +
+
+ {'author' in page.data && page.data.author && ( +
+

WRITTEN BY

+
+ {(() => { + const info = authors[page.data.author ?? '']; + if (!info) return null; + return ( + <> + {info.avatar ? ( + {`${info.name}'s + ) : ( +
+ + + +
+ )} +
+

{info.name}

+ {info.title &&

{info.title}

} + {info.bio &&

{info.bio}

} +
+ + ); + })()} +
+
+ )} +
+ ); +} diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx new file mode 100644 index 0000000..174f713 --- /dev/null +++ b/app/blog/layout.tsx @@ -0,0 +1,79 @@ +import Link from 'next/link'; + +export default function BlogLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+
+
+
+ +
+

+ Parseable Blog +

+
+
+
+ Home + Documentation + API + Release Notes +
+
+ +
+
+
+
+ {children} +
+ +
+
+ ); +} diff --git a/app/blog/page.tsx b/app/blog/page.tsx new file mode 100644 index 0000000..fc03bf4 --- /dev/null +++ b/app/blog/page.tsx @@ -0,0 +1,131 @@ +import { blogSource } from '@/lib/source'; +import { notFound } from 'next/navigation'; +import Link from 'next/link'; + +export default function BlogPage() { + const pages = blogSource.getPages(); + + if (!pages || pages.length === 0) { + notFound(); + } + + // Sort pages by date (newest first) + const sortedPages = [...pages].sort((a, b) => { + const dateA = a.data.date ? new Date(a.data.date).getTime() : 0; + const dateB = b.data.date ? new Date(b.data.date).getTime() : 0; + return dateB - dateA; + }); + + // Featured articles (first 3) + const featuredArticles = sortedPages.slice(0, 3); + + // Recent posts (next 4 or all available) + const recentPosts = sortedPages.slice(0, 4); + + // We'll use the same posts for featured and recent sections + + return ( +
+ {/* Hero Section */} +
+
+
+
+

+ Exploring the Latest in Parseable Logging +

+
+
+
+
+ + {/* Featured Articles */} +

+ Featured Articles +

+
+
+ {featuredArticles.map((page) => ( +
+
+
+ + {page.data.title} + +

{page.data.description}

+
+
+ ))} +
+
+ + {/* Recent Posts */} +

+ Recent Posts +

+
+ {recentPosts.map((page) => ( +
+
+
+ + {page.data.title} + +

{page.data.description}

+ {page.data.date && ( +

+ {new Date(page.data.date).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + })} +

+ )} +
+
+ ))} +
+ + {/* Newsletter Section */} +
+
+
+

+ Stay Updated +

+

+ Get the latest Parseable updates delivered straight to your inbox. +

+
+
+ +
+
+
+
+ ); +} diff --git a/app/layout.config.tsx b/app/layout.config.tsx index d141b2d..c4686e1 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -1,5 +1,5 @@ import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared"; -import { IconMonkeybar, IconApi, IconMapShare } from "@tabler/icons-react"; +import { IconMonkeybar, IconApi, IconMapShare, IconArticle } from "@tabler/icons-react"; /** * Shared layout configurations @@ -19,6 +19,12 @@ export const baseOptions: BaseLayoutProps = { external: true, icon: , // Icon for the link }, + { + text: "Blog", + url: "/blog", + external: false, // Internal link + icon: , // Icon for the link + }, { text: "Release Notes", url: "/docs/release-notes", diff --git a/content/blog/_category_.json b/content/blog/_category_.json new file mode 100644 index 0000000..21c6b00 --- /dev/null +++ b/content/blog/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Blog", + "position": 1 +} diff --git a/content/blog/introducing-parseable.mdx b/content/blog/introducing-parseable.mdx new file mode 100644 index 0000000..ce8564c --- /dev/null +++ b/content/blog/introducing-parseable.mdx @@ -0,0 +1,47 @@ +--- +title: Introducing Parseable +description: A modern, cloud-native logging solution built for scale +date: "2023-06-15" +author: nitish +--- + +# Introducing Parseable + +Parseable is a modern, cloud-native logging solution designed to handle logs at scale. Built with performance and simplicity in mind, Parseable offers a powerful alternative to traditional logging systems. + +## Why Parseable? + +Traditional logging solutions often struggle with the volume and velocity of logs generated by modern applications. Parseable addresses these challenges by: + +- **High Performance**: Built on Rust and Apache Arrow for blazing-fast log ingestion and querying +- **Cloud Native**: Designed to run seamlessly in Kubernetes and other cloud environments +- **Simple Setup**: Get started in minutes with minimal configuration +- **SQL Querying**: Query your logs using familiar SQL syntax +- **Schema Enforcement**: Ensure log quality with schema validation + +## Getting Started + +Getting started with Parseable is easy: + +```bash +docker run -p 8000:8000 parseablehq/parseable:latest +``` + +Once running, you can send logs to Parseable using simple HTTP requests: + +```bash +curl -X POST http://localhost:8000/api/v1/logstream/my-app \ + -H "Content-Type: application/json" \ + -d '[{"message": "Hello, Parseable!", "level": "info"}]' +``` + +## What's Next? + +In the coming weeks, we'll be sharing more about Parseable's features, use cases, and best practices. Stay tuned for: + +- Deep dives into Parseable's architecture +- Performance benchmarks +- Integration guides for popular frameworks +- Advanced querying techniques + +We're excited to have you join us on this journey to better logging! diff --git a/content/blog/optimizing-log-queries.mdx b/content/blog/optimizing-log-queries.mdx new file mode 100644 index 0000000..d5c9157 --- /dev/null +++ b/content/blog/optimizing-log-queries.mdx @@ -0,0 +1,81 @@ +--- +title: Optimizing Log Queries in Parseable +description: Tips and techniques for writing efficient log queries +date: "2023-07-20" +author: deb +--- + +# Optimizing Log Queries in Parseable + +Effective log analysis depends on your ability to write efficient queries. In this post, we'll explore techniques to optimize your Parseable log queries for better performance and insights. + +## Understanding Parseable's Query Engine + +Parseable uses a powerful SQL-based query engine built on Apache Arrow. This architecture provides several advantages: + +- Column-oriented storage for faster analytical queries +- Vectorized query execution +- Predicate pushdown for efficient filtering +- Parallel query processing + +## Query Optimization Techniques + +### 1. Use Time-Based Filtering + +Always include time constraints in your queries to limit the data scanned: + +```sql +SELECT * FROM logs +WHERE timestamp >= '2023-07-01T00:00:00Z' +AND timestamp < '2023-07-02T00:00:00Z' +``` + +### 2. Select Only Needed Columns + +Instead of using `SELECT *`, specify only the columns you need: + +```sql +SELECT timestamp, level, message, service_name +FROM logs +WHERE level = 'error' +``` + +### 3. Leverage Indexed Fields + +Parseable automatically indexes certain fields. Use these for faster filtering: + +```sql +SELECT * FROM logs +WHERE service_name = 'api-gateway' +AND level = 'error' +``` + +### 4. Use Aggregations Wisely + +Aggregations can help summarize large volumes of data: + +```sql +SELECT + service_name, + COUNT(*) as error_count +FROM logs +WHERE level = 'error' +GROUP BY service_name +ORDER BY error_count DESC +``` + +## Monitoring Query Performance + +Parseable provides query execution statistics to help you understand and optimize performance: + +- Query execution time +- Bytes scanned +- Rows processed + +Use these metrics to identify and refine slow-performing queries. + +## Conclusion + +By following these optimization techniques, you can significantly improve the performance of your log queries in Parseable, enabling faster troubleshooting and more efficient log analysis. + +Stay tuned for more advanced query techniques in our upcoming posts! diff --git a/lib/authors.ts b/lib/authors.ts new file mode 100644 index 0000000..c82c2a5 --- /dev/null +++ b/lib/authors.ts @@ -0,0 +1,19 @@ +export interface AuthorProfile { + name: string; + title?: string; + avatar?: string; // path in public + bio?: string; + twitter?: string; + github?: string; +} + +export const authors: Record = { + nitish: { + name: 'Nitish Tiwari', + title: 'Founder, Parseable', + avatar: '/avatars/generic.svg', + twitter: 'https://twitter.com/nitish_tiwari', + github: 'https://github.com/nitish-tiwari', + bio: 'Logs, Rust and open-source enthusiast.' + }, +}; diff --git a/lib/source.ts b/lib/source.ts index dba7790..0f34490 100644 --- a/lib/source.ts +++ b/lib/source.ts @@ -1,4 +1,4 @@ -import { docs, releaseNotes } from '@/.source'; +import { docs, releaseNotes, blog } from '@/.source'; import { loader } from 'fumadocs-core/source'; import { createOpenAPI, attachFile } from 'fumadocs-openapi/server'; @@ -22,6 +22,15 @@ export const releaseNotesSource = loader({ }, }); +// Create a separate loader for blog posts +export const blogSource = loader({ + baseUrl: '/blog', + source: blog.toFumadocsSource(), + pageTree: { + attachFile, + }, +}); + // Create OpenAPI instance for the Parseable API export const openapi = createOpenAPI({ // Point to the correct schema file diff --git a/package.json b/package.json index 1a16292..01c4f79 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "next": "15.3.1", "next-mdx-remote": "^5.0.0", "react": "^19.1.0", - "react-dom": "^19.1.0" + "react-dom": "^19.1.0", + "zod": "^3.25.64" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c51d90..dac90eb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: react-dom: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) + zod: + specifier: ^3.25.64 + version: 3.25.64 devDependencies: '@tailwindcss/postcss': specifier: ^4.1.5 @@ -3708,8 +3711,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod@3.24.4: - resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==} + zod@3.25.64: + resolution: {integrity: sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -6037,7 +6040,7 @@ snapshots: next: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) picocolors: 1.1.1 unist-util-visit: 5.0.0 - zod: 3.24.4 + zod: 3.25.64 transitivePeerDependencies: - acorn - supports-color @@ -8067,6 +8070,6 @@ snapshots: yocto-queue@0.1.0: {} - zod@3.24.4: {} + zod@3.25.64: {} zwitch@2.0.4: {} diff --git a/public/avatars/generic.svg b/public/avatars/generic.svg new file mode 100644 index 0000000..3d85600 --- /dev/null +++ b/public/avatars/generic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/blog-hero.jpg b/public/blog-hero.jpg new file mode 100644 index 0000000..e69de29 diff --git a/public/blog-placeholder.jpg b/public/blog-placeholder.jpg new file mode 100644 index 0000000..e69de29 diff --git a/source.config.ts b/source.config.ts index 7798e36..aa4d28f 100644 --- a/source.config.ts +++ b/source.config.ts @@ -5,6 +5,7 @@ import { metaSchema, } from 'fumadocs-mdx/config'; import { remarkAdmonition } from 'fumadocs-core/mdx-plugins'; +import { z } from 'zod'; // Define the documentation collection export const docs = defineDocs({ @@ -30,6 +31,24 @@ export const releaseNotes = defineDocs({ }, }); +// Define a custom schema for blog posts that includes the date field +const blogFrontmatterSchema = frontmatterSchema.extend({ + date: z.string().optional(), + author: z.string().optional(), +}); + +// Define the blog collection +export const blog = defineDocs({ + // The root directory for blog posts + dir: 'content/blog', + docs: { + schema: blogFrontmatterSchema, + }, + meta: { + schema: metaSchema, + }, +}); + // Configure MDX options export default defineConfig({ mdxOptions: {