Persistent inserts in a UDF even if the function aborts
When an exception occurs everything is rolled back. You want to execute selected code "outside" the current transaction context, which is commonly referred to as autonomous transactions. This is not currently implemented (as of pg 9.4). There is an item in the Postgres TODO wiki, but it's a tricky matter, don't hold your breath.

For now you can use the additional module dblink. It provides functions to connect to another DB in a separate connection (and thus also separate transaction) to execute SQL there. What's done is done and cannot be rolled back. By connecting to the same DB another time you effectively achieve autonomous transactions (with some extra overhead). It's decently fast.

Proof of concept

Preparations

You need to install dblink once per database (where you run the function):

CREATE EXTENSION dblink;


For convenience I encapsulate the connection info in a FOREIGN SERVER plus USER MAPPING:

CREATE SERVER myserver FOREIGN DATA WRAPPER dblink_fdw
OPTIONS (hostaddr '127.0.0.1', dbname 'test');

CREATE USER MAPPING FOR role_source SERVER myserver
OPTIONS (user 'role_target', password 'secret');


There are various options for the connection string. The example is for a user named role_source to connect as role_target in a DB test on localhost 127.0.0.1. Could be the same role name for source and target. I use a password here (simple example) but I much prefer password-less access for the purpose, so we don't have to save a PW (and it does not end up in backups etc.):


Run batch file with psql command without password


However, per documentation:


  Only superusers may use dblink_connect to create
  non-password-authenticated connections. If non-superusers need this
  capability, use dblink_connect_u instead.


Or create a SECURITY DEFINER function to encapsulate relevant parts. See below.

Main function

CREATE OR REPLACE FUNCTION f_work()
  RETURNS void AS
$func$
DECLARE
   sql1 text := 'SELECT 1';  -- dummy code
   sql2 text := 'SELECT 2';

BEGIN
   PERFORM dblink_connect('myserver');  -- name of foreign server
   PERFORM dblink_exec($f$INSERT INTO backup_logs(sql)
                          VALUES ('f_work() start')$f$);   -- log start

   PERFORM dblink_exec('INSERT INTO backup_logs(sql)
                        SELECT ' || quote_literal(sql1));  -- log sql1
   EXECUTE sql1;

   -- RAISE EXCEPTION 'foo'; -- unquote to test error case

   PERFORM dblink_exec('INSERT INTO backup_logs(sql)
                        SELECT ' || quote_literal(sql2));  -- log sql2
   EXECUTE sql2;


   PERFORM dblink_exec($f$INSERT INTO backup_logs(sql)
                          VALUES ('f_work() end')$f$);     -- log end
   PERFORM dblink_disconnect();
END
$func$  LANGUAGE plpgsql VOLATILE;


Be sure to have a USER MAPPING for the role you are running this function with, or make it a SECURITY DEFINER function owned by the role you want to work with:


Permission for sequence in another schema
Creating UUID extension as the non super privileged user